Files
hereconnect/apps/frontend/src/components/ChatSubscribeWebPush.vue
T

231 lines
7.9 KiB
Vue
Raw Normal View History

2024-12-02 08:50:25 +02:00
<template>
<div class="text-sm">
<div class="flex items-center gap-2">
<div v-if="isPending" class="w-5 h-5">
<ProgressSpinner style="width: 100%; height: 100%" strokeWidth="8" fill="transparent" />
</div>
<Checkbox v-else v-model="enabled" inputId="webPush" name="delivery" binary />
<label for="webPush">Показать уведомление, когда придет ответ</label>
</div>
<!-- Инструкции для iOS -->
<div
v-if="showRequireStandaloneInstructionsForIOS"
class="bg-yellow-100 text-yellow-800 p-4 rounded my-4"
>
<p class="mb-2">Чтобы включить уведомления, добавьте это приложение на главный экран:</p>
<ul class="list-disc pl-5">
<li class="mb-1">
Нажмите
<strong class="font-semibold">
"<img
src="@/assets/icons/apple-share.svg?url"
alt="Поделиться"
class="h-7 -mt-2 inline-block"
/>"
</strong>
внизу экрана
</li>
<li class="mb-1">
Выберите
<strong class="inline-flex items-baseline font-semibold">
<span class="me-1">"На экран «Домой»</span>
<img
src="@/assets/icons/apple-system-plus.svg?url"
alt=""
class="h-4 inline-block self-center"
/>"
</strong>
</li>
<li>Откройте приложение с главного экрана и повторите попытку.</li>
</ul>
</div>
<!-- Ошибка разрешений -->
<p v-else-if="isError" class="text-red-500 mb-2">{{ error?.message || error }}<br /></p>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useMutation } from '@tanstack/vue-query'
import {
getVapidPublicKey,
getWebPushSubscriptionDocument,
saveWebPushSubscription,
} from '@/api/webPush'
import { getUserChatSettingsDocument } from '@/api/userChatSettings'
import type { UserChatSettingsDocument } from '@hereconnect/types'
import { isNotFoundError } from '@/api/PouchDB'
import { saveUserChatSettings } from '@/api/userChatSettings'
2024-12-02 10:01:23 +02:00
import { getQrCodeDocument } from '@/api/qrCode'
2024-12-02 08:50:25 +02:00
// Проверки iOS и запуска из главного экрана
const isIOS = computed(
() => /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window && window.MSStream),
)
const isStandalone = computed(() => 'standalone' in window.navigator && window.navigator.standalone)
const { qr_code_uri, chat, user_chat_role } = defineProps<{
qr_code_uri: UserChatSettingsDocument['qr_code_uri']
chat: UserChatSettingsDocument['chat']
user_chat_role: UserChatSettingsDocument['user_chat_role']
}>()
const enabled = ref<boolean>(false)
const subscribeOptionsPromise = getVapidPublicKey().then((applicationServerKey) => ({
userVisibleOnly: true,
applicationServerKey,
}))
// Функция запроса разрешений и подписки
async function requestNotificationPermission() {
if (!('Notification' in window)) {
throw new Error(
isIOS.value && !isStandalone.value
? 'Добавьте приложение на главный экран, чтобы использовать уведомления.'
: 'Уведомления не поддерживаются этим браузером.',
)
}
const permission = await Notification.requestPermission()
if (permission !== 'granted') {
throw new Error(`Разрешение на уведомления не предоставлено: ${permission}`)
}
const registration = await navigator.serviceWorker.ready
2024-12-02 10:01:23 +02:00
const subscription = await registration.pushManager
.subscribe(await subscribeOptionsPromise)
.catch(async (error) => {
const oldSubscription = await registration.pushManager.getSubscription()
if (!oldSubscription) {
throw error
}
await oldSubscription.unsubscribe()
return registration.pushManager.subscribe(await subscribeOptionsPromise)
})
2024-12-02 08:50:25 +02:00
return saveWebPushSubscription(subscription)
}
const getPushSubscription = async () => {
try {
if ('serviceWorker' in navigator && 'PushManager' in window) {
const registration = await navigator.serviceWorker.ready
const permission = await registration.pushManager.permissionState(
await subscribeOptionsPromise,
)
console.debug('permission', permission)
if (permission === 'prompt') {
return
}
if (permission !== 'granted') {
throw new Error(`Разрешение на уведомления не предоставлено: ${permission}`)
}
const subscription = await registration.pushManager.getSubscription()
if (subscription) {
console.debug('Уже есть активная подписка:', subscription)
return subscription
} else {
console.debug('Нет активной подписки на Push-уведомления.')
}
} else {
console.warn('Push уведомления или Service Worker не поддерживаются в этом браузере.')
}
} catch (error) {
console.error('Произошла ошибка при получении подписки:', error)
throw error
}
}
const stage = ref<'receivePushSubscription' | 'createPushSubscription' | 'removePushSubscription'>(
'receivePushSubscription',
)
// Хуки для состояния запроса
const { isPending, isError, error, mutate } = useMutation({
mutationKey: [stage],
mutationFn: async () => {
console.debug(stage.value)
try {
if (stage.value === 'receivePushSubscription') {
const chatSettingsDocument = await getUserChatSettingsDocument({
qr_code_uri,
chat,
user_chat_role,
}).catch((error) => {
if (!isNotFoundError(error)) throw error
})
2024-12-02 10:01:23 +02:00
if (
(chatSettingsDocument && chatSettingsDocument.is_push_notification_enabled) ||
(!chatSettingsDocument &&
user_chat_role === 'owner' &&
(await getQrCodeDocument(qr_code_uri)).messageDeliveryMethod === 'webPush')
) {
2024-12-02 08:50:25 +02:00
const [subscription, subscriptionDocument] = await Promise.all([
getPushSubscription(),
getWebPushSubscriptionDocument().catch((error) => {
if (!isNotFoundError(error)) throw error
}),
])
if (subscription) {
if (!subscriptionDocument) {
await saveWebPushSubscription(subscription)
}
enabled.value = true
stage.value = 'removePushSubscription'
} else {
stage.value = 'createPushSubscription'
enabled.value = false
}
} else {
stage.value = 'createPushSubscription'
}
} else if (stage.value === 'createPushSubscription') {
await requestNotificationPermission()
await saveUserChatSettings({
qr_code_uri,
chat,
user_chat_role,
is_push_notification_enabled: true,
})
} else if (stage.value === 'removePushSubscription') {
await saveUserChatSettings({
qr_code_uri,
chat,
user_chat_role,
is_push_notification_enabled: false,
})
}
} catch (error) {
enabled.value = false
throw error
}
return null
},
retry: false,
})
watch(
[() => stage.value, () => enabled.value],
() => {
if (stage.value === 'removePushSubscription') {
if (!enabled.value) mutate()
} else if (stage.value === 'createPushSubscription') {
if (enabled.value) mutate()
} else {
mutate()
}
},
{ immediate: true },
)
const showRequireStandaloneInstructionsForIOS = computed(
() => isError.value && isIOS.value && !isStandalone.value && enabled.value,
)
</script>