update sw
Deploy app frontend / app frontend (push) Successful in 1m29s

This commit is contained in:
2024-12-05 11:04:04 +02:00
parent 9c0ce14013
commit ddcda323a1
6 changed files with 217 additions and 192 deletions
+5 -5
View File
@@ -45,11 +45,11 @@ jobs:
- name: deploy production - name: deploy production
run: mv -v /www/hereconnect.condev.ru/current-prod /www/hereconnect.condev.ru/old-prod && mv -v /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-prod /www/hereconnect.condev.ru/current-prod && rm -rf /www/hereconnect.condev.ru/old-prod run: mv -v /www/hereconnect.condev.ru/current-prod /www/hereconnect.condev.ru/old-prod && mv -v /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-prod /www/hereconnect.condev.ru/current-prod && rm -rf /www/hereconnect.condev.ru/old-prod
# - name: build dev - name: build dev
# run: cd build/frontend && bun vite build --emptyOutDir --mode development --outDir /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-dev/ run: cd build/frontend && bun vite build --emptyOutDir --mode development --outDir /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-dev/
#
# - name: deploy dev - name: deploy dev
# run: mv -v /www/hereconnect.condev.ru/current-dev /www/hereconnect.condev.ru/old-dev && mv -v /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-dev /www/hereconnect.condev.ru/current-dev && rm -rf /www/hereconnect.condev.ru/old-dev run: mv -v /www/hereconnect.condev.ru/current-dev /www/hereconnect.condev.ru/old-dev && mv -v /www/hereconnect.condev.ru/build-${{env.GITHUB_SHA}}-dev /www/hereconnect.condev.ru/current-dev && rm -rf /www/hereconnect.condev.ru/old-dev
- name: cleanup build directories - name: cleanup build directories
if: always() if: always()
+106 -149
View File
@@ -1,189 +1,146 @@
const VER = 6
self.addEventListener('message', async (event) => {
event.waitUntil(debug(event.source, Object.assign({ answer: true }, event.data)))
})
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
console.log('install event:', event) console.log('install event:', event)
// The promise that skipWaiting() returns can be safely ignored. // The promise that skipWaiting() returns can be safely ignored.
self.skipWaiting() event.waitUntil(self.skipWaiting().then(() => debug(event.source, { installed: true })))
// Perform any other actions required for your // Perform any other actions required for your
// service worker to install, potentially inside // service worker to install, potentially inside
// of event.waitUntil(); // of event.waitUntil();
}) })
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent)
self.addEventListener('activate', (event) => { self.addEventListener('activate', (event) => {
event.waitUntil( event.waitUntil(
clients.claim(), // Привязка нового сервис-воркера к текущим клиентам // Привязка нового сервис-воркера к текущим клиентам
clients.claim().then(() => debug(event.source, { activated: true })),
) )
}) })
self.addEventListener('push', (event) => { self.addEventListener('push', (event) => {
const data = event.data.json() // PushData keys structure standart https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
console.log('Push событие получено:', data.type) let pushData = event.data.json()
if (!pushData || !pushData.notification.title) {
// Отображение уведомления console.error('Received WebPush with an empty title. Received body: ', pushData)
if (data.type === 'notification') {
event.waitUntil(
self.registration
.showNotification(data.notification.title, data.notification.options)
.then(() => {
console.log('Notification создан', data.notification.options.tag)
})
.catch((error) => {
console.error('Notification ошибка создания', error)
}),
)
} else if (data.type === 'cancelNotification') {
event.waitUntil(
self.registration
.getNotifications({ tag: data.notification.options.tag })
.then((notifications) => {
notifications.forEach((notification) => {
if (
notification.tag === data.notification.options.tag ||
notification.data.payload.tag === data.notification.options.data.payload.tag // удалить, если notification.tag поддерживают все
) {
notification.close()
console.log('Notification удален')
} }
self.registration
.showNotification(pushData.notification.title, pushData.notification.options)
.then(function () {
// You can save to your analytics fact that push was shown
// fetch('https://your_backend_server.com/track_show?message_id=' + pushData.data.message_id);
}) })
}),
)
}
}) })
self.addEventListener('notificationclose', function (event) { async function handleNotificationClick(event) {
const dismissedNotification = event.notification
console.log('dismissedNotification', dismissedNotification)
})
self.addEventListener('notificationclick', (event) => {
console.log('click to notification', event.notification)
event.notification.close() event.notification.close()
// const urlToOpen = new URL(event.notification.data.url, self.location.origin).href if (!event.notification.data) {
await debug(null, {
error: 'Click on WebPush with empty data, where url should be. Notification',
})
console.error(
'Click on WebPush with empty data, where url should be. Notification: ',
event.notification,
)
return
}
if (!event.notification.data.url) { if (!event.notification.data.url) {
console.log('no urlToOpen') await debug(null, { error: 'Click on WebPush without url. Notification' })
console.error('Click on WebPush without url. Notification: ', event.notification)
return return
} }
function getNotificationUrl(urlToOpen) { await openURL(event.notification.data.url)
return urlToOpen
// return fetch(urlToOpen, {
// redirect: 'manual',
// method: 'HEAD',
// cache: 'no-cache',
// })
// .then((response) => {
// console.log('response.url', response.url)
// console.log('response.type', response.url)
// if (response && response.type === 'opaqueredirect') {
// const location = response.headers.get('location')
// console.log('headers location', location)
// return location
// }
// return response.url
// })
// .catch((error) => {
// console.debug('erro in getNotificationUrl', error)
// return urlToOpen
// })
} }
// Открытие URL из данных уведомления self.addEventListener('notificationclick', (event) => {
event.waitUntil( event.waitUntil(handleNotificationClick(event))
Promise.all([ })
getNotificationUrl(event.notification.data.url),
clients.matchAll({ type: 'window', includeUncontrolled: true }), async function openURL(urlToOpen) {
]) let matchedClients
.then(([urlToOpen, clientList]) => { try {
const urlToOpenObj = new URL(urlToOpen, location.origin) const urlToOpenObj = new URL(urlToOpen, location.origin)
for (const client of clientList) {
console.log('client.url', client.url) matchedClients = await clients.matchAll({ type: 'window', includeUncontrolled: true })
try {
for (const client of matchedClients) {
const clientUrlObj = new URL(client.url) const clientUrlObj = new URL(client.url)
try {
if ( if (
urlToOpenObj.pathname + urlToOpenObj.search === urlToOpenObj.pathname + urlToOpenObj.search !==
clientUrlObj.pathname + clientUrlObj.search clientUrlObj.pathname + clientUrlObj.search
) { ) {
console.log('focus') await debug(client, {
urlToOpenObj: urlToOpenObj.pathname + urlToOpenObj.search,
return Promise.all([ clientUrlObj: clientUrlObj.pathname + clientUrlObj.search,
'focus' in client && client.focus(),
client.postMessage({
type: 'notificationClick',
tag: event.notification.tag,
payload: event.notification.payload,
url: urlToOpen,
matchUrl: true,
isIOS,
}),
])
}
}
if (isIOS && clientList.length > 0) {
const client = clientList[0]
return Promise.all([
client.focus(),
client.postMessage({
type: 'notificationClick',
tag: event.notification.tag,
payload: event.notification.payload,
url: urlToOpen,
matchUrl: false,
isIOS,
}),
])
}
if (clients.openWindow) {
console.log('open new window', urlToOpen)
return clients.openWindow(urlToOpen).then((client) => {
if (client) {
return client.postMessage({
type: 'notificationClick',
tag: event.notification.tag,
payload: event.notification.payload,
url: urlToOpen,
matchUrl: true,
isIOS,
})
} else {
console.log('new window not opened')
for (const client of clientList) {
return client.postMessage({
type: 'notificationClick',
tag: event.notification.tag,
payload: event.notification.payload,
url: urlToOpen,
matchUrl: false,
isIOS,
}) })
continue
} }
}
})
} else {
console.log('clients.openWindow not available')
for (const client of clientList) { if (client.focused) {
return client.postMessage({ return
type: 'notificationClick',
tag: event.notification.tag,
payload: event.notification.payload,
url: urlToOpen,
matchUrl: false,
isIOS,
})
} }
}
})
.catch((error) => { await client.focus()
return
} catch (error) {
await debug({ error: error.message })
}
}
} catch (error) {
console.error('get client error', error)
}
} catch (error) {
console.error('openURL error', error)
}
try {
const client = await clients.openWindow(urlToOpen)
if (!client) {
await debug(client, { error: 'client is null' })
throw new Error('client is null')
}
} catch (error) {
await debug(matchedClients && matchedClients[0], { error: error.message })
console.error(error) console.error(error)
}), }
) }
async function debug(client, data) {
try {
if (!client) {
const matchedClients = await clients.matchAll({ type: 'window', includeUncontrolled: true })
for (const matchedClient of matchedClients) {
if (matchedClient.focused) {
client = matchedClient
break
}
}
if (!client && matchedClients.length) {
return Promise.all(matchedClients.map((client) => debug(client, data)))
}
}
if (!client) {
throw new Error('not found client for debug')
}
if (!client.postMessage) {
console.error('not found client.postMessage for debug', client)
return
}
await client.postMessage({
type: 'debug',
debug: Object.assign({ VER }, data || {}),
}) })
} catch (error) {
console.error(error)
}
}
+54
View File
@@ -1,7 +1,61 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import { registerServiceWorker } from '@/registerServiceWorker'
const router = useRouter()
const debugEnabled = ref(import.meta.env.DEV)
const debugItems = ref<string[]>([])
const debug = (...data: unknown[]) => {
if (debugEnabled.value) {
debugItems.value.push(JSON.stringify(data))
}
}
provide('debug', debug)
registerServiceWorker()
.then(() => {
console.log('Service Worker успешно зарегистрирован')
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data.type === 'debug') {
if ('debug' in event.data && typeof event.data.debug === 'object') {
debug(event.data.debug)
}
} else if (event.data.type === 'notificationClick' && !event.data.matchUrl) {
const url = new URL(event.data.url, location.origin)
console.debug('go to', `${url.pathname}${url.search}${url.hash}`)
router.push(`${url.pathname}${url.search}${url.hash}`)
}
})
})
.catch(console.error)
</script> </script>
<template> <template>
<div class="debug gap-2 py-2 flex flex-col" v-if="debugEnabled">
<div class="px-2 text-nowrap" v-for="(value, key) in debugItems" :key>
<Button
icon="pi pi-trash"
class="p-button-danger p-button-text"
size="small"
@click="debugItems.splice(key, 1)"
/>
<span class="font-mono text-xs">{{ value }}</span>
</div>
</div>
<RouterView /> <RouterView />
</template> </template>
<style scoped>
.debug {
position: fixed;
top: 0;
left: 0;
max-width: 100vw;
max-height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
overflow-y: scroll;
color: white;
}
</style>
@@ -61,6 +61,8 @@ import { isNotFoundError } from '@/api/PouchDB'
import { saveUserChatSettings } from '@/api/userChatSettings' import { saveUserChatSettings } from '@/api/userChatSettings'
import { getQrCodeDocument } from '@/api/qrCode' import { getQrCodeDocument } from '@/api/qrCode'
const debug: (...data: unknown[]) => void = inject('debug')!
// Проверки iOS и запуска из главного экрана // Проверки iOS и запуска из главного экрана
const isIOS = computed( const isIOS = computed(
() => /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window && window.MSStream), () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window && window.MSStream),
@@ -99,7 +101,7 @@ async function requestNotificationPermission() {
const subscription = await registration.pushManager const subscription = await registration.pushManager
.subscribe(await subscribeOptionsPromise) .subscribe(await subscribeOptionsPromise)
.catch(async (error) => { .catch(async (error) => {
console.debug('subscribe error', error) debug('subscribe error', error)
const oldSubscription = await registration.pushManager.getSubscription() const oldSubscription = await registration.pushManager.getSubscription()
if (!oldSubscription) { if (!oldSubscription) {
throw error throw error
@@ -119,26 +121,23 @@ const getPushSubscription = async () => {
const permission = await registration.pushManager.permissionState( const permission = await registration.pushManager.permissionState(
await subscribeOptionsPromise, await subscribeOptionsPromise,
) )
console.debug('permission', permission) debug('permission', permission)
if (permission === 'prompt') { if (permission === 'denied') {
return
}
if (permission !== 'granted') {
throw new Error(`Разрешение на уведомления не предоставлено: ${permission}`) throw new Error(`Разрешение на уведомления не предоставлено: ${permission}`)
} }
const subscription = await registration.pushManager.getSubscription() const subscription = await registration.pushManager.getSubscription()
if (subscription) { if (subscription) {
console.debug('Уже есть активная подписка:', subscription) debug('Уже есть активная подписка:', subscription)
return subscription return subscription
} else { } else {
console.debug('Нет активной подписки на Push-уведомления.') debug('Нет активной подписки на Push-уведомления.')
} }
} else { } else {
console.warn('Push уведомления или Service Worker не поддерживаются в этом браузере.') debug('Push уведомления или Service Worker не поддерживаются в этом браузере.')
} }
} catch (error) { } catch (error) {
console.error('Произошла ошибка при получении подписки:', error) debug('Произошла ошибка при получении подписки:', error)
throw error throw error
} }
} }
@@ -151,7 +150,7 @@ const stage = ref<'receivePushSubscription' | 'createPushSubscription' | 'remove
const { isPending, isError, error, mutate } = useMutation({ const { isPending, isError, error, mutate } = useMutation({
mutationKey: [stage], mutationKey: [stage],
mutationFn: async () => { mutationFn: async () => {
console.debug('stage', stage.value) debug({ stage: stage.value })
try { try {
if (stage.value === 'receivePushSubscription') { if (stage.value === 'receivePushSubscription') {
const chatSettingsDocument = await getUserChatSettingsDocument({ const chatSettingsDocument = await getUserChatSettingsDocument({
@@ -160,13 +159,16 @@ const { isPending, isError, error, mutate } = useMutation({
user_chat_role, user_chat_role,
}).catch((error) => { }).catch((error) => {
if (!isNotFoundError(error)) { if (!isNotFoundError(error)) {
console.debug('chatSettingsDocument error not found - its ok', error) debug('chatSettingsDocument error', error)
throw error throw error
} }
console.debug('chatSettingsDocument error', error) debug('chatSettingsDocument not found') // it's ok
}) })
if (chatSettingsDocument) { if (chatSettingsDocument) {
console.debug('chatSettingsDocument', chatSettingsDocument) debug(
'chatSettingsDocument.is_push_notification_enabled',
chatSettingsDocument.is_push_notification_enabled,
)
} }
if ( if (
(chatSettingsDocument && chatSettingsDocument.is_push_notification_enabled) || (chatSettingsDocument && chatSettingsDocument.is_push_notification_enabled) ||
@@ -186,9 +188,11 @@ const { isPending, isError, error, mutate } = useMutation({
} }
enabled.value = true enabled.value = true
stage.value = 'removePushSubscription' stage.value = 'removePushSubscription'
debug('subscribed')
} else { } else {
stage.value = 'createPushSubscription' stage.value = 'createPushSubscription'
enabled.value = false enabled.value = false
debug('have to createPushSubscription')
} }
} else { } else {
stage.value = 'createPushSubscription' stage.value = 'createPushSubscription'
@@ -211,7 +215,7 @@ const { isPending, isError, error, mutate } = useMutation({
} }
} catch (error) { } catch (error) {
enabled.value = false enabled.value = false
console.debug('unknown error at stage ', stage.value, error) debug('unknown error at stage ', stage.value, error)
throw error throw error
} }
return null return null
+1 -15
View File
@@ -31,7 +31,6 @@ const MyPreset = definePreset(Aura, {
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { registerServiceWorker } from '@/registerServiceWorker'
const app = createApp(App) const app = createApp(App)
@@ -61,21 +60,8 @@ app.use(PrimeVue, {
}, },
}, },
}) })
app.config.globalProperties.$swDebug = ref([])
app.mount('#app') app.mount('#app')
registerServiceWorker()
.then(() => {
console.log('Service Worker успешно зарегистрирован')
navigator.serviceWorker.addEventListener('message', (event) => {
console.debug('serviceWorker message', event.data)
if (event.data.type === 'notificationClick' && !event.data.matchUrl) {
const url = new URL(event.data.url, location.origin)
console.debug('go to', `${url.pathname}${url.search}${url.hash}`)
router.push(`${url.pathname}${url.search}${url.hash}`)
}
})
})
.catch(console.error)
window.PouchDB = PouchDB // it for debug in console only window.PouchDB = PouchDB // it for debug in console only
@@ -3,9 +3,33 @@ export async function registerServiceWorker() {
throw new Error('Ваш браузер не поддерживает Service Worker.') throw new Error('Ваш браузер не поддерживает Service Worker.')
} }
function requestDebugVersion() {
navigator.serviceWorker?.controller?.postMessage({
type: 'requestDebugVersion',
})
}
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Service Worker controller has changed.')
requestDebugVersion()
})
try { try {
const registration = await navigator.serviceWorker.register('/service-worker.js') const registration = await navigator.serviceWorker.register('/service-worker.js')
console.log('Service Worker зарегистрирован:', registration) console.log('Service Worker зарегистрирован:', registration)
requestDebugVersion()
window.addEventListener(
'focus',
() => {
navigator.serviceWorker?.controller?.postMessage({
type: 'focused',
})
},
{},
)
return registration return registration
} catch (error) { } catch (error) {
console.error('Ошибка регистрации Service Worker:', error) console.error('Ошибка регистрации Service Worker:', error)