86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
"use client"
|
|
|
|
export type DesktopNotificationPermission = NotificationPermission | "unsupported"
|
|
|
|
export function desktopNotificationsSupported(): boolean {
|
|
return typeof window !== "undefined" && "Notification" in window
|
|
}
|
|
|
|
export function getDesktopNotificationPermission(): DesktopNotificationPermission {
|
|
if (!desktopNotificationsSupported()) return "unsupported"
|
|
return Notification.permission
|
|
}
|
|
|
|
export async function requestDesktopNotificationPermission(): Promise<DesktopNotificationPermission> {
|
|
if (!desktopNotificationsSupported()) return "unsupported"
|
|
if (Notification.permission === "granted") return "granted"
|
|
if (Notification.permission === "denied") return "denied"
|
|
try {
|
|
return await Notification.requestPermission()
|
|
} catch {
|
|
return Notification.permission
|
|
}
|
|
}
|
|
|
|
export function isReplyOrMentionSubject(subject: string): boolean {
|
|
const normalized = subject.trim().toLowerCase()
|
|
return (
|
|
normalized.startsWith("re:") ||
|
|
normalized.startsWith("ré:") ||
|
|
normalized.startsWith("fwd:") ||
|
|
normalized.startsWith("tr:")
|
|
)
|
|
}
|
|
|
|
let audioContext: AudioContext | null = null
|
|
|
|
function playNotificationTone() {
|
|
if (typeof window === "undefined") return
|
|
try {
|
|
audioContext ??= new AudioContext()
|
|
const ctx = audioContext
|
|
const oscillator = ctx.createOscillator()
|
|
const gain = ctx.createGain()
|
|
oscillator.type = "sine"
|
|
oscillator.frequency.value = 880
|
|
gain.gain.value = 0.04
|
|
oscillator.connect(gain)
|
|
gain.connect(ctx.destination)
|
|
oscillator.start()
|
|
oscillator.stop(ctx.currentTime + 0.12)
|
|
} catch {}
|
|
}
|
|
|
|
export function showDesktopNotification(options: {
|
|
title: string
|
|
body?: string
|
|
tag?: string
|
|
onClick?: () => void
|
|
playSound?: boolean
|
|
}) {
|
|
if (!desktopNotificationsSupported()) return false
|
|
if (Notification.permission !== "granted") return false
|
|
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
const notification = new Notification(options.title, {
|
|
body: options.body,
|
|
tag: options.tag,
|
|
icon: "/brand/ultimail-mark.png",
|
|
})
|
|
if (options.onClick) {
|
|
notification.onclick = () => {
|
|
window.focus()
|
|
options.onClick?.()
|
|
notification.close()
|
|
}
|
|
}
|
|
if (options.playSound) playNotificationTone()
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|