/** * 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 { 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("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 { if (!isTauriRuntime()) return try { const token = await readPushToken() if (token) { await apiClient.post("/devices/unregister", { push_token: token }) } } catch { /* best effort */ } }