Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/components/Settings/BrowserTab/NotificationState.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@

<script lang="ts">
import { ref } from 'vue'
import { requestNotificationPermission } from '/@/lib/notification/requestPermission'
import AToggle from '/@/components/UI/AToggle.vue'

const useNotificationPermission = () => {
const permission = ref<NotificationPermission>()
permission.value = window.Notification?.permission
permission.value = Notification?.permission

const requestPermission = async () => {
// permission.valueがundefinedでないときは、
// 上の取得の仕方からNotificationが存在していることが確定している
permission.value = await requestNotificationPermission()
permission.value = await Notification.requestPermission()
}

return { permission, requestPermission }
Expand Down
22 changes: 12 additions & 10 deletions src/components/Toast/AToast.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ const iconNameMap: Record<Toast['type'], string> = {
const useAutoHide = (props: { toast: Toast }) => {
const { deleteToast } = useToastStore()

let timer: number | undefined
let timer: NodeJS.Timeout

const remove = () => {
deleteToast(props.toast.id)
}

onMounted(() => {
timer = window.setTimeout(() => {
remove()
}, props.toast.timeout)
})
onUnmounted(() => {
window.clearTimeout(timer)
})
if (props.toast.timeout < Infinity) {
onMounted(() => {
timer = setTimeout(() => {
remove()
}, props.toast.timeout)
})
onUnmounted(() => {
clearTimeout(timer)
})
}

return { remove }
}
Expand All @@ -56,7 +58,7 @@ const { remove } = useAutoHide(props)

const onClick = () => {
if (props.toast.onClick) {
props.toast.onClick()
props.toast.onClick(props.toast.id)
} else {
remove()
}
Expand Down
38 changes: 29 additions & 9 deletions src/lib/notification/notification.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type { NotificationClickEvent } from '/@/types/InlineNotificationReplies'
import apis from '/@/lib/apis'
import router from '/@/router'
import { isIOSApp } from '/@/lib/dom/browser'
import { isIOSApp, isPWA, isWebKit } from '/@/lib/dom/browser'
import type { ChannelId, DMChannelId } from '/@/types/entity-ids'
import { createNotificationArgumentsCreator } from './notificationArguments'
import type { OnCanUpdate } from './updateToast'
import { setupUpdateToast } from './updateToast'
import type { FirebasePayloadData } from './firebase'
import { setupFirebaseApp, getFirebaseApp } from './firebase'
import { requestNotificationPermission } from './requestPermission'
import {
getMessaging,
onMessage,
getToken as getTokenFb,
deleteToken as deleteTokenFb
} from 'firebase/messaging'
import { wait } from '/@/lib/basic/timer'
import { useToastStore } from '/@/store/ui/toast'

const appName = window.traQConfig.name || 'traQ'
const ignoredChannels = window.traQConfig.inlineReplyDisableChannels ?? []
Expand Down Expand Up @@ -48,7 +48,7 @@ const notify = async (
return regist.showNotification(notificationTitle, notificationOptions)
}
}
if (window.Notification?.permission === 'granted') {
if (Notification?.permission === 'granted') {
return new Notification(notificationTitle, notificationOptions)
}
return null
Expand All @@ -63,10 +63,30 @@ export const connectFirebase = async (onCanUpdate: OnCanUpdate) => {
}
}

if (window.Notification) {
if (Notification) {
if (Notification.permission === 'default') {
// 上でNotificationが存在していることを確認している
const permission = await requestNotificationPermission()
// 上で Notification が存在していることを確認している
const permission = await (() => {
// WebKit ではユーザージェスチャーを起点としたポップアップのみ許可される
if (isWebKit() && isPWA()) {
const { addToast, deleteToast } = useToastStore()

return new Promise<NotificationPermission>((resolve, reject) => {
addToast({
type: 'info',
text: '【通知を有効にしてください】\nメッセージ受信時に通知が届くようになります。(クリックで許可)',
timeout: Infinity,
onClick: id => {
deleteToast(id)
Notification.requestPermission().then(resolve).catch(reject)
}
})
})
} else {
return Notification.requestPermission()
}
})()

if (permission === 'granted') {
notify({ title: `ようこそ${appName}へ!!` }, true)
} else {
Expand Down Expand Up @@ -102,9 +122,9 @@ export const connectFirebase = async (onCanUpdate: OnCanUpdate) => {

setupUpdateToast(registration, onCanUpdate)

if (window.Notification?.permission !== 'granted') {
if (Notification?.permission !== 'granted') {
// eslint-disable-next-line no-console
console.warn(`[Notification] permission ${window.Notification?.permission}`)
console.warn(`[Notification] permission ${Notification?.permission}`)
return
}
if (!firebaseApp) {
Expand Down Expand Up @@ -162,7 +182,7 @@ export const connectFirebase = async (onCanUpdate: OnCanUpdate) => {
}

export const deleteToken = () => {
if (window.Notification?.permission !== 'granted') return
if (Notification?.permission !== 'granted') return

const firebaseApp = getFirebaseApp()
const messaging = getMessaging(firebaseApp)
Expand Down
12 changes: 0 additions & 12 deletions src/lib/notification/requestPermission.ts

This file was deleted.

20 changes: 8 additions & 12 deletions src/store/ui/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface Toast {
*
* デフォルトはトーストの削除
*/
onClick?: () => unknown
onClick?: (id: number) => unknown
/**
* 自動付与されるid
*/
Expand All @@ -39,28 +39,24 @@ const useToastStorePinia = defineStore('ui/toast', () => {
let nextId = 0

const addToast = (toast: ToastOption) => {
const id = nextId++
toasts.value.unshift({
...toast,
timeout: toast.timeout ?? DEFAULT_TOAST_TIMEOUT,
id: nextId
id
})
nextId++

while (MAX_TOAST_COUNT < toasts.value.length) {
toasts.value.pop()
}
}

const addSuccessToast = (text: string) => {
addToast({ type: 'success', text })
}
const addErrorToast = (text: string) => {
addToast({ type: 'error', text })
}
const addInfoToast = (text: string) => {
addToast({ type: 'info', text })
return id
}

const addSuccessToast = (text: string) => addToast({ type: 'success', text })
const addErrorToast = (text: string) => addToast({ type: 'error', text })
const addInfoToast = (text: string) => addToast({ type: 'info', text })

const deleteToast = (id: number) => {
const index = toasts.value.findIndex(toast => toast.id === id)
toasts.value.splice(index, 1)
Expand Down