fix: always upsert push token on mobile start
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import Constants from "expo-constants";
|
||||
import * as Device from "expo-device";
|
||||
import * as Notifications from "expo-notifications";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { Platform } from "react-native";
|
||||
import { apiClient } from "@/api/client";
|
||||
import { logError } from "@/lib/errors";
|
||||
import { routeNotificationTap } from "@/lib/notification-routing";
|
||||
|
||||
const STORED_TOKEN_KEY = "expo_push_token";
|
||||
|
||||
let configured = false;
|
||||
let tokenListenerSubscription: Notifications.Subscription | null = null;
|
||||
|
||||
@@ -49,9 +46,9 @@ export function configureNotifications(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires (or returns the cached) Expo push token for this device. Returns
|
||||
* null on simulators, when permission is denied, or when any step fails — the
|
||||
* caller should treat that as "no push, no further action".
|
||||
* Acquires the Expo push token for this device. Returns null on simulators,
|
||||
* when permission is denied, or when any step fails — the caller should treat
|
||||
* that as "no push, no further action".
|
||||
*/
|
||||
async function acquirePushToken(): Promise<string | null> {
|
||||
if (!Device.isDevice) return null;
|
||||
@@ -78,43 +75,18 @@ async function acquirePushToken(): Promise<string | null> {
|
||||
return tokenResult.data;
|
||||
}
|
||||
|
||||
async function getStoredToken(): Promise<string | null> {
|
||||
try {
|
||||
return await SecureStore.getItemAsync(STORED_TOKEN_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setStoredToken(token: string): Promise<void> {
|
||||
try {
|
||||
await SecureStore.setItemAsync(STORED_TOKEN_KEY, token);
|
||||
} catch (err) {
|
||||
logError(err, { scope: "push.store" });
|
||||
}
|
||||
}
|
||||
|
||||
async function clearStoredToken(): Promise<void> {
|
||||
try {
|
||||
await SecureStore.deleteItemAsync(STORED_TOKEN_KEY);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the freshly-fetched token to whatever we last sent to Orion and
|
||||
* only POSTs on a delta. Never throws — push registration is best-effort and
|
||||
* must never block the auth path.
|
||||
* Posts the current Expo token to Orion. Runs on every launch and on token
|
||||
* rotation — the backend upsert is idempotent so re-posting is cheap, and
|
||||
* skipping the local dedupe means an env switch (e.g. dev → prod build on the
|
||||
* same device) re-registers correctly. Never throws — push registration is
|
||||
* best-effort and must never block the auth path.
|
||||
*/
|
||||
export async function syncPushToken(token?: string | null): Promise<void> {
|
||||
try {
|
||||
const next = token ?? (await acquirePushToken());
|
||||
if (!next) return;
|
||||
|
||||
const stored = await getStoredToken();
|
||||
if (stored === next) return;
|
||||
|
||||
const platform = Platform.OS === "ios" ? "ios" : "android";
|
||||
const appVersion = Constants.expoConfig?.version ?? "";
|
||||
|
||||
@@ -123,28 +95,22 @@ export async function syncPushToken(token?: string | null): Promise<void> {
|
||||
platform,
|
||||
app_version: appVersion,
|
||||
});
|
||||
await setStoredToken(next);
|
||||
} catch (err) {
|
||||
logError(err, { scope: "push.sync" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort unregister at sign-out. Wipes the stored token even if the
|
||||
* server call fails so the next signed-in user re-registers cleanly.
|
||||
* Best-effort unregister at sign-out. Fetches the current token (without
|
||||
* prompting — getPermissionsAsync is read-only) and deletes it server-side.
|
||||
*/
|
||||
export async function unregisterPushToken(): Promise<void> {
|
||||
try {
|
||||
const stored = await getStoredToken();
|
||||
if (stored) {
|
||||
try {
|
||||
await apiClient.unregisterPushToken(stored);
|
||||
} catch (err) {
|
||||
logError(err, { scope: "push.unregister" });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await clearStoredToken();
|
||||
const token = await acquirePushToken();
|
||||
if (!token) return;
|
||||
await apiClient.unregisterPushToken(token);
|
||||
} catch (err) {
|
||||
logError(err, { scope: "push.unregister" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user