flotilla/src/app/push.ts

132 lines
3.5 KiB
TypeScript
Raw Normal View History

2025-06-24 21:15:36 +00:00
import * as nip19 from "nostr-tools/nip19"
2025-06-19 17:22:17 +00:00
import {Capacitor} from "@capacitor/core"
2025-06-24 21:15:36 +00:00
import type {ActionPerformed, RegistrationError, Token} from "@capacitor/push-notifications"
import {PushNotifications} from "@capacitor/push-notifications"
2025-06-27 15:33:31 +00:00
import {parseJson, poll} from "@welshman/lib"
2025-06-24 21:15:36 +00:00
import {isSignedEvent} from "@welshman/util"
import {goto} from "$app/navigation"
2025-06-30 17:25:34 +00:00
import {ucFirst} from "@lib/util"
2025-06-19 17:22:17 +00:00
import {VAPID_PUBLIC_KEY} from "@app/state"
export const platform = Capacitor.getPlatform()
2025-06-30 17:25:34 +00:00
export const platformName = platform === "ios" ? "iOS" : ucFirst(platform)
2025-06-24 21:15:36 +00:00
export const initializePushNotifications = () => {
if (platform === "web") return
PushNotifications.addListener("pushNotificationActionPerformed", (action: ActionPerformed) => {
const event = parseJson(action.notification.data.event)
const parsedRelays = parseJson(action.notification.data.relays)
const relays = Array.isArray(parsedRelays) ? parsedRelays : []
if (isSignedEvent(event)) {
goto("/" + nip19.neventEncode({id: event.id, relays}))
}
})
}
2025-06-19 17:22:17 +00:00
export const canSendPushNotifications = () => ["web", "android", "ios"].includes(platform)
export const getWebPushInfo = async () => {
if (!("serviceWorker" in navigator)) {
throw new Error("Service Worker not supported")
}
if (!("PushManager" in window)) {
2025-07-09 21:00:42 +00:00
throw new Error("Push notifications are not supported")
2025-06-19 17:22:17 +00:00
}
if (Notification.permission === "denied") {
throw new Error("Push notifications are blocked")
}
if (Notification.permission !== "granted") {
const permission = await Notification.requestPermission()
if (permission !== "granted") {
throw new Error("Push notification permission denied")
}
}
const registration = await navigator.serviceWorker.ready
let subscription = await registration.pushManager.getSubscription()
if (!subscription) {
subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: VAPID_PUBLIC_KEY,
})
}
2025-06-24 21:15:36 +00:00
const {keys} = subscription.toJSON()
2025-06-19 17:22:17 +00:00
if (!keys) {
throw new Error(`Failed to get push info: no keys were returned`)
}
return {
endpoint: subscription.endpoint,
p256dh: keys.p256dh,
auth: keys.auth,
}
}
2025-06-27 15:33:31 +00:00
export type PushInfo = {
device_token: string
bundle_identifier?: string
}
2025-06-24 21:15:36 +00:00
export const getCapacitorPushInfo = async () => {
let status = await PushNotifications.checkPermissions()
if (status.receive === "prompt") {
status = await PushNotifications.requestPermissions()
}
if (status.receive !== "granted") {
throw new Error("Failed to register for push notifications")
}
let device_token = ""
let error = "Failed to register for push notifications"
PushNotifications.addListener("registration", (token: Token) => {
device_token = token.value
})
PushNotifications.addListener("registrationError", (_error: RegistrationError) => {
error = _error.error
})
await PushNotifications.register()
2025-06-27 15:33:31 +00:00
await poll({
condition: () => Boolean(device_token),
signal: AbortSignal.timeout(5000),
})
2025-06-24 21:15:36 +00:00
if (!device_token) {
throw new Error(error)
}
2025-06-19 17:22:17 +00:00
2025-06-27 15:33:31 +00:00
const info: PushInfo = {device_token}
if (platform === "ios") {
info.bundle_identifier = "social.flotilla"
}
return info
2025-06-19 17:22:17 +00:00
}
export const getPushInfo = (): Promise<Record<string, string>> => {
switch (platform) {
case "web":
return getWebPushInfo()
case "ios":
case "android":
2025-06-24 21:15:36 +00:00
return getCapacitorPushInfo()
2025-06-19 17:22:17 +00:00
default:
throw new Error(`Invalid push platform: ${platform}`)
}
}