130 lines
4.5 KiB
Vue
130 lines
4.5 KiB
Vue
<template>
|
|
<CreateMessageDeliveryOption :isLoading :label :method v-model="model" />
|
|
|
|
<!-- Инструкции для 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 && model === method" class="text-red-500 mb-2">
|
|
{{ error }}.<br />
|
|
<button @click="retry()" class="text-blue-500 underline">Повторить</button>
|
|
</p>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { type QRCodeDocument } from '@hereconnect/types'
|
|
import { type DeliveryOption, DELIVERY_METHOD_WEB_PUSH } from '@/constants/deliveryOptions'
|
|
import { computed } from 'vue'
|
|
import { useQuery } from '@tanstack/vue-query'
|
|
import { getVapidPublicKey, saveWebPushSubscription } from '@/api/webPush'
|
|
|
|
// Проверки 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)
|
|
|
|
// Пропсы
|
|
type DeliveryOptionWebPush = Extract<DeliveryOption, { method: typeof DELIVERY_METHOD_WEB_PUSH }>
|
|
const props = defineProps<{
|
|
label: DeliveryOptionWebPush['label']
|
|
method: DeliveryOptionWebPush['method']
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'change'): void
|
|
}>()
|
|
|
|
const model = defineModel<null | QRCodeDocument['messageDeliveryMethod']>()
|
|
|
|
// Функция запроса разрешений и подписки
|
|
async function requestNotificationPermission() {
|
|
if (!('Notification' in window)) {
|
|
throw new Error(
|
|
isIOS.value && !isStandalone.value
|
|
? 'Добавьте приложение на главный экран, чтобы использовать уведомления.'
|
|
: 'Уведомления не поддерживаются этим браузером.',
|
|
)
|
|
}
|
|
|
|
try {
|
|
const subscribeOptionsPromise = getVapidPublicKey().then((applicationServerKey) => ({
|
|
userVisibleOnly: true,
|
|
applicationServerKey,
|
|
}))
|
|
|
|
const permission = await Notification.requestPermission()
|
|
if (permission !== 'granted') {
|
|
throw new Error('Разрешение на уведомления не предоставлено.')
|
|
}
|
|
|
|
const registration = await navigator.serviceWorker.ready
|
|
|
|
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)
|
|
})
|
|
|
|
const webPushSubscriptionDoc = await saveWebPushSubscription(subscription)
|
|
emit('change')
|
|
|
|
return webPushSubscriptionDoc
|
|
} catch (err) {
|
|
throw new Error('Ошибка при создании Push-подписки: ' + (err as Error).message)
|
|
}
|
|
}
|
|
|
|
// Хуки для состояния запроса
|
|
const {
|
|
isLoading,
|
|
isError,
|
|
error,
|
|
refetch: retry,
|
|
} = useQuery({
|
|
enabled: computed(() => model.value === DELIVERY_METHOD_WEB_PUSH),
|
|
queryKey: ['createPushSubscription'],
|
|
queryFn: () => requestNotificationPermission(),
|
|
retry: false,
|
|
})
|
|
|
|
const showRequireStandaloneInstructionsForIOS = computed(
|
|
() => isError.value && isIOS.value && !isStandalone.value && model.value === props.method,
|
|
)
|
|
</script>
|