push notification works
Deploy app frontend / app frontend (push) Successful in 1m32s

This commit is contained in:
2024-12-05 21:06:57 +02:00
parent 23aced4071
commit d57e1569b3
8 changed files with 115 additions and 117 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ jobs:
VITE_MANIFEST_NAME: НаСвязи VITE_MANIFEST_NAME: НаСвязи
VITE_MANIFEST_SHORT_NAME: НаСвязи VITE_MANIFEST_SHORT_NAME: НаСвязи
VITE_MANIFEST_ID: https://hereconnect.condev.ru VITE_MANIFEST_ID: https://hereconnect.condev.ru
VITE_MANIFEST_START_URL: https://hereconnect.condev.ru/manage/dashboard #VITE_MANIFEST_START_URL: https://hereconnect.condev.ru/
VITE_MANIFEST_SCOPE: https://hereconnect.condev.ru/ #VITE_MANIFEST_SCOPE: https://hereconnect.condev.ru/
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1
View File
@@ -30,6 +30,7 @@ declare module 'vue' {
ProgressSpinner: typeof import('primevue/progressspinner')['default'] ProgressSpinner: typeof import('primevue/progressspinner')['default']
QRCodeCard: typeof import('./src/components/manage/QRCodeCard.vue')['default'] QRCodeCard: typeof import('./src/components/manage/QRCodeCard.vue')['default']
QueryRender: typeof import('./src/components/QueryRender.vue')['default'] QueryRender: typeof import('./src/components/QueryRender.vue')['default']
RadioButton: typeof import('primevue/radiobutton')['default']
ReadContactInfo: typeof import('./src/components/read/ReadContactInfo.vue')['default'] ReadContactInfo: typeof import('./src/components/read/ReadContactInfo.vue')['default']
ReadMessageSender: typeof import('./src/components/read/ReadMessageSender.vue')['default'] ReadMessageSender: typeof import('./src/components/read/ReadMessageSender.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
+48 -62
View File
@@ -1,13 +1,12 @@
const VER = 7 const VER = 14
self.addEventListener('message', async (event) => { self.addEventListener('message', async (event) => {
event.waitUntil(debug(event.source, Object.assign({ answer: true }, event.data))) event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
}) })
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
console.log('install event:', event)
// The promise that skipWaiting() returns can be safely ignored. // The promise that skipWaiting() returns can be safely ignored.
event.waitUntil(self.skipWaiting().then(() => debug(event.source, { installed: true }))) event.waitUntil(self.skipWaiting().then(() => debug({ 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
@@ -21,23 +20,34 @@ self.addEventListener('activate', (event) => {
) )
}) })
self.addEventListener('push', (event) => { self.addEventListener('push', async (event) => {
debug({
event: 'push',
})
// PushData keys structure standart https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification // PushData keys structure standart https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
let pushData = event.data.json() let pushData = event.data.json()
if (!pushData || !pushData.notification.title) { if (!pushData || !pushData.notification.title) {
console.error('Received WebPush with an empty title. Received body: ', pushData) debug({
error: 'Received WebPush with an empty title. Received body',
})
} }
event.waitUntil( self.registration
self.registration .showNotification(pushData.notification.title, pushData.notification.options)
.showNotification(pushData.notification.title, pushData.notification.options) .then(function () {
.then(function () { // You can save to your analytics fact that push was shown
// 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);
// fetch('https://your_backend_server.com/track_show?message_id=' + pushData.data.message_id); })
}), .catch((error) => {
) debug({
errorShowNotification: error.message,
})
})
}) })
self.addEventListener('notificationclick', (event) => { self.addEventListener('notificationclick', (event) => {
debug({
event: 'notificationclick',
})
event.waitUntil(handleNotificationClick(event)) event.waitUntil(handleNotificationClick(event))
}) })
@@ -45,18 +55,13 @@ async function handleNotificationClick(event) {
event.notification.close() event.notification.close()
if (!event.notification.data) { if (!event.notification.data) {
await debug(null, { await debug({
error: 'Click on WebPush with empty data, where url should be. Notification', 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 return
} }
if (!event.notification.data.url) { if (!event.notification.data.url) {
await debug(null, { error: 'Click on WebPush without url. Notification' }) await debug({ error: 'Click on WebPush without url. Notification' })
console.error('Click on WebPush without url. Notification: ', event.notification)
return return
} }
@@ -74,75 +79,56 @@ async function openURL(urlToOpen) {
for (const client of matchedClients) { for (const client of matchedClients) {
const clientUrlObj = new URL(client.url) const clientUrlObj = new URL(client.url)
try { try {
await debug(client, {
urlToOpen,
'client.url': client.url,
})
if ( if (
urlToOpenObj.pathname + urlToOpenObj.search !== urlToOpenObj.pathname + urlToOpenObj.search !==
clientUrlObj.pathname + clientUrlObj.search clientUrlObj.pathname + clientUrlObj.search
) { ) {
await debug(client, {
urlToOpenObj: urlToOpenObj.pathname + urlToOpenObj.search,
clientUrlObj: clientUrlObj.pathname + clientUrlObj.search,
})
continue continue
} }
if (client.focused) { if (client.focused) {
debug({ 'client.focused': true })
return return
} }
await client.focus() await client.focus()
debug({ 'client.focus()': true })
return return
} catch (error) { } catch (error) {
await debug({ error: error.message }) await debug({ error: error.message })
} }
} }
} catch (error) { } catch (error) {
console.error('get client error', error) debug({ 'get client error': error })
} }
} catch (error) { } catch (error) {
console.error('openURL error', error) debug({ 'openURL error': error })
}
if (matchedClients && matchedClients.length) {
debug({ 'no matched clients': true })
} }
try { try {
const client = await clients.openWindow(urlToOpen) const client = await clients.openWindow(urlToOpen)
if (!client) { if (!client) {
await debug(client, { error: 'client is null' }) await debug({ error: 'client is null' })
throw new Error('client is null')
} }
debug({ 'clients.openWindow()': true })
} catch (error) { } catch (error) {
await debug(matchedClients && matchedClients[0], { error: error.message }) await debug(matchedClients && matchedClients[0], { error: error.message })
console.error(error)
} }
} }
async function debug(client, data) { function debug() {}
try { // async function debug(...args) {
if (!client) { // return fetch('https://hereconnect-debug.condev.ru/debug', {
const matchedClients = await clients.matchAll({ type: 'window', includeUncontrolled: true }) // method: 'POST',
for (const matchedClient of matchedClients) { // body: JSON.stringify(args),
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)
}
}
+18 -17
View File
@@ -2,8 +2,8 @@
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import { registerServiceWorker } from '@/registerServiceWorker' import { registerServiceWorker } from '@/registerServiceWorker'
const router = useRouter() // const router = useRouter()
const debugEnabled = ref(import.meta.env.DEV) const debugEnabled = ref(false) // import.meta.env.DEV)
const debugItems = ref<string[]>([]) const debugItems = ref<string[]>([])
const debug = (...data: unknown[]) => { const debug = (...data: unknown[]) => {
if (debugEnabled.value) { if (debugEnabled.value) {
@@ -14,21 +14,21 @@ const debug = (...data: unknown[]) => {
provide('debug', debug) provide('debug', debug)
registerServiceWorker() registerServiceWorker()
.then(() => { // .then(() => {
console.log('Service Worker успешно зарегистрирован') // // console.log('Service Worker успешно зарегистрирован')
navigator.serviceWorker.addEventListener('message', (event) => { // navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data.type === 'debug') { // if (event.data.type === 'debug') {
if ('debug' in event.data && typeof event.data.debug === 'object') { // if ('debug' in event.data && typeof event.data.debug === 'object') {
debug(event.data.debug) // debug(event.data.debug)
} // }
} else if (event.data.type === 'notificationClick' && !event.data.matchUrl) { // } else if (event.data.type === 'notificationclick' && !event.data.matchUrl) {
const url = new URL(event.data.url, location.origin) // const url = new URL(event.data.url, location.origin)
console.debug('go to', `${url.pathname}${url.search}${url.hash}`) // // console.debug('go to', `${url.pathname}${url.search}${url.hash}`)
router.push(`${url.pathname}${url.search}${url.hash}`) // router.push(`${url.pathname}${url.search}${url.hash}`)
} // }
}) // })
}) // })
.catch(console.error) // .catch(console.error)
</script> </script>
<template> <template>
@@ -43,6 +43,7 @@ registerServiceWorker()
<span class="font-mono text-xs">{{ value }}</span> <span class="font-mono text-xs">{{ value }}</span>
</div> </div>
</div> </div>
<RouterView /> <RouterView />
</template> </template>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"short_name": "НаСвязи", "short_name": "НаСвязи",
"id": "hereconnect", "id": "hereconnect",
"start_url": "/manage/dashboard", "start_url": "/",
"theme_color": "#ffffff", "theme_color": "#ffffff",
"background_color": "#ffffff", "background_color": "#ffffff",
"display": "standalone", "display": "standalone",
+28 -28
View File
@@ -3,36 +3,36 @@ export async function registerServiceWorker() {
throw new Error('Ваш браузер не поддерживает Service Worker.') throw new Error('Ваш браузер не поддерживает Service Worker.')
} }
function requestDebugVersion() { // function requestDebugVersion() {
navigator.serviceWorker?.controller?.postMessage({ // navigator.serviceWorker?.controller?.postMessage({
type: 'requestDebugVersion', // type: 'requestDebugVersion',
}) // })
} // }
navigator.serviceWorker.addEventListener('controllerchange', () => { navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Service Worker controller has changed.') // console.log('Service Worker controller has changed.')
requestDebugVersion() // 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() // requestDebugVersion()
//
window.addEventListener( // window.addEventListener(
'focus', // 'focus',
() => { // () => {
navigator.serviceWorker?.controller?.postMessage({ // navigator.serviceWorker?.controller?.postMessage({
type: 'focused', // type: 'focused',
}) // })
}, // },
{}, // {},
) // )
//
return registration return registration
} catch (error) { // } catch (error) {
console.error('Ошибка регистрации Service Worker:', error) // console.error('Ошибка регистрации Service Worker:', error)
throw error // throw error
} // }
} }
+8 -7
View File
@@ -1,23 +1,24 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { ROUTE_NAMES } from '@/constants/routes' import { ROUTE_NAMES } from '@/constants/routes'
import HomeView from '@/views/HomeView.vue'
function isRunningStandalone() {
return window.matchMedia('(display-mode: standalone)').matches
}
const routes = [ const routes = [
{ {
path: '/', path: '/',
name: ROUTE_NAMES.HOME, name: ROUTE_NAMES.HOME,
component: HomeView, component: () =>
isRunningStandalone()
? import('@/views/ManageDashboardView.vue')
: import('@/views/HomeView.vue'),
}, },
{ {
path: '/create', path: '/create',
name: ROUTE_NAMES.CREATE, name: ROUTE_NAMES.CREATE,
component: () => import('@/views/CreateView.vue'), component: () => import('@/views/CreateView.vue'),
}, },
{
path: '/app/start',
name: ROUTE_NAMES.APP_START,
component: () => import('@/views/ManageDashboardView.vue'),
},
{ {
path: '/manage/dashboard', path: '/manage/dashboard',
name: ROUTE_NAMES.MANAGE_DASHBOARD, name: ROUTE_NAMES.MANAGE_DASHBOARD,
+9
View File
@@ -133,6 +133,15 @@ export default defineConfig(({ mode }) => {
res.end(JSON.stringify(result, null, 2)) res.end(JSON.stringify(result, null, 2))
}, },
} satisfies RouteConfig, } satisfies RouteConfig,
'POST /debug': {
async handler(req, res) {
process.stdout.write('\ndebug: ')
req.pipe(process.stdout)
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end('true')
},
} satisfies RouteConfig,
}, },
}), }),
{ {