diff --git a/apps/frontend/public/css/offline.css b/apps/frontend/public/css/offline.css
new file mode 100644
index 0000000..03f5470
--- /dev/null
+++ b/apps/frontend/public/css/offline.css
@@ -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;
+}
diff --git a/apps/frontend/public/offline.html b/apps/frontend/public/offline.html
new file mode 100644
index 0000000..a6d848d
--- /dev/null
+++ b/apps/frontend/public/offline.html
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+ Загрузка… — НаСвязи
+
+
+
+
+
+
Нет подключения к интернету
+
Проверьте подключение к сети или попробуйте ещё раз.
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/public/service-worker.js b/apps/frontend/public/service-worker.js
index 04338c2..2e76d07 100644
--- a/apps/frontend/public/service-worker.js
+++ b/apps/frontend/public/service-worker.js
@@ -1,25 +1,155 @@
-// const VER = 14
+// const VER = 18
-self.addEventListener('message', async (event) => {
- event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
-})
+const CACHE_NAME = 'cache-v1'
+const TEMP_PAGE_URL = '/offline.html'
+const STATIC_ASSETS = [TEMP_PAGE_URL, '/css/offline.css']
self.addEventListener('install', (event) => {
- // The promise that skipWaiting() returns can be safely ignored.
- event.waitUntil(self.skipWaiting().then(() => debug({ installed: true })))
-
- // Perform any other actions required for your
- // service worker to install, potentially inside
- // of event.waitUntil();
+ event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)))
})
self.addEventListener('activate', (event) => {
event.waitUntil(
- // Привязка нового сервис-воркера к текущим клиентам
- clients.claim().then(() => debug(event.source, { activated: true })),
+ caches.keys().then((cacheNames) =>
+ 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) => {
debug({
event: 'push',
diff --git a/apps/frontend/src/App.vue b/apps/frontend/src/App.vue
index 58f185c..362fe88 100644
--- a/apps/frontend/src/App.vue
+++ b/apps/frontend/src/App.vue
@@ -13,7 +13,15 @@ const debug = (...data: unknown[]) => {
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(() => {
// // console.log('Service Worker успешно зарегистрирован')
// navigator.serviceWorker.addEventListener('message', (event) => {
diff --git a/apps/frontend/src/layouts/LayoutWithTabs.vue b/apps/frontend/src/layouts/LayoutWithTabs.vue
index eed41e5..88b2ad9 100644
--- a/apps/frontend/src/layouts/LayoutWithTabs.vue
+++ b/apps/frontend/src/layouts/LayoutWithTabs.vue
@@ -22,12 +22,12 @@
Будем рады
-
вашей поддержке
-
+
diff --git a/apps/frontend/src/registerServiceWorker.ts b/apps/frontend/src/registerServiceWorker.ts
index 5e2eb95..0c98bba 100644
--- a/apps/frontend/src/registerServiceWorker.ts
+++ b/apps/frontend/src/registerServiceWorker.ts
@@ -29,7 +29,7 @@ export async function registerServiceWorker() {
// },
// {},
// )
- //
+
return registration
// } catch (error) {
// console.error('Ошибка регистрации Service Worker:', error)
diff --git a/apps/frontend/src/views/ReadQRCodeView.vue b/apps/frontend/src/views/ReadQRCodeView.vue
index b2809d6..1f6c363 100644
--- a/apps/frontend/src/views/ReadQRCodeView.vue
+++ b/apps/frontend/src/views/ReadQRCodeView.vue
@@ -27,17 +27,17 @@
>
Нужен такой же?
- Создайте свой QR‑кодСоздайте свой QR‑код.
- Сделайте НаСвязи удобнее. Будем рады вашей поддержкеподдержке.