82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
/**
|
|
* App-side push notifications.
|
|
*
|
|
* The native push plugin (FCM on Android / APNs on iOS) obtains the device
|
|
* token and either caches it in the secure store under "push_token" or emits a
|
|
* `ulti://push-token` event. After login we register it with the backend; on
|
|
* logout we unregister it.
|
|
*
|
|
* Backend contract:
|
|
* POST /api/v1/devices/register { platform, app, push_token, device_id? } -> { id }
|
|
* POST /api/v1/devices/unregister { push_token }
|
|
*/
|
|
import { apiClient } from "@/lib/api/client"
|
|
import { invoke, listen } from "@/lib/native/bridge"
|
|
import { isTauriRuntime, SUITE_APP } from "@/lib/platform"
|
|
import { readPushToken, writePushToken } from "@/lib/native/secure-store"
|
|
|
|
type PushRegistration = { platform: string; token: string | null }
|
|
|
|
function normalizePlatform(os: string): "ios" | "android" | null {
|
|
if (os === "ios") return "ios"
|
|
if (os === "android") return "android"
|
|
return null
|
|
}
|
|
|
|
let started = false
|
|
|
|
async function registerToken(platform: "ios" | "android", token: string) {
|
|
await writePushToken(token)
|
|
await apiClient.post<{ id: string }>("/devices/register", {
|
|
platform,
|
|
app: SUITE_APP,
|
|
push_token: token,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Call after a successful login. Idempotent. Asks the native layer for the push
|
|
* token and registers it; also subscribes to late token deliveries.
|
|
*/
|
|
export async function registerPushAfterLogin(): Promise<void> {
|
|
if (!isTauriRuntime()) return
|
|
|
|
// Late token delivery (e.g. permission granted after first launch).
|
|
if (!started) {
|
|
started = true
|
|
await listen("ulti://push-token", (payload) => {
|
|
const token = typeof payload === "string" ? payload : null
|
|
const platform = normalizePlatform(
|
|
(navigator?.platform || "").toLowerCase().includes("android")
|
|
? "android"
|
|
: "ios"
|
|
)
|
|
if (token && platform) void registerToken(platform, token)
|
|
})
|
|
}
|
|
|
|
try {
|
|
const reg = await invoke<PushRegistration>("plugin:ulti-core|push_register")
|
|
if (!reg) return
|
|
const platform = normalizePlatform(reg.platform)
|
|
if (platform && reg.token) {
|
|
await registerToken(platform, reg.token)
|
|
}
|
|
} catch {
|
|
/* push optional — never block login */
|
|
}
|
|
}
|
|
|
|
/** Call before clearing the session. */
|
|
export async function unregisterPushOnLogout(): Promise<void> {
|
|
if (!isTauriRuntime()) return
|
|
try {
|
|
const token = await readPushToken()
|
|
if (token) {
|
|
await apiClient.post("/devices/unregister", { push_token: token })
|
|
}
|
|
} catch {
|
|
/* best effort */
|
|
}
|
|
}
|