Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.1 kB · 218 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219import messaging from "@react-native-firebase/messaging";import { openAuthSessionAsync } from "expo-web-browser";import { PermissionsAndroid, Platform } from "react-native";import { AppStore } from "store";import { StateCreator } from "zustand";
export interface PlatformSlice { status: "idle" | "loading" | "failed"; notificationToken: string | null; notificationDestination: string | null; // actions handleNotification: (payload?: { [key: string]: string | object }) => void; clearNotification: () => void; openLoginLink: (url: string) => Promise<void>; initPushNotifications: () => Promise<void>; registerNotificationToken: () => Promise<void>; // web-only actions; no-ops on native. Present so the shared PlatformSlice // type is identical across platforms and the settings toggle can call // them unconditionally. enableWebNotifications: () => Promise<NotificationPermission>; disableWebNotifications: () => Promise<void>; webNotificationPermission: () => NotificationPermission;}
const checkApplicationPermission = async () => { if (Platform.OS === "android") { try { await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS, ); } catch (error) { console.log("error getting notifications ", error); } }};
export const createPlatformSlice: StateCreator< AppStore, [], [], PlatformSlice> = (set, get) => ({ status: "idle", notificationToken: null, notificationDestination: null, handleNotification: (payload) => { if (!payload) return; if (typeof payload.path !== "string") return; set({ notificationDestination: payload.path }); }, clearNotification: () => { set({ notificationDestination: null }); }, openLoginLink: async (url: string) => { set({ status: "loading" }); try { const res = await openAuthSessionAsync(url); set({ status: "idle" }); if (res.type === "success") { // do oauth callback get().oauthCallback(res.url); } } catch (error) { set({ status: "failed" }); } }, initPushNotifications: async () => { try { const msg = messaging();
// Handle background messages (when app is backgrounded) if (msg && typeof msg.setBackgroundMessageHandler === "function") { msg.setBackgroundMessageHandler(async (remoteMessage) => { console.log("Message handled in the background!", remoteMessage); }); }
// Request runtime permission on Android (POST_NOTIFICATIONS) await checkApplicationPermission();
// Request notification permission from the OS const authorizationStatus = await msg.requestPermission();
if (authorizationStatus === messaging.AuthorizationStatus.AUTHORIZED) { console.log("User has notification permissions enabled."); } else if ( authorizationStatus === messaging.AuthorizationStatus.PROVISIONAL ) { console.log("User has provisional notification permissions."); } else { console.log("User has notification permissions disabled"); }
// Fetch device token const token = await msg.getToken(); if (token) { set({ notificationToken: token }); console.log("Notification token acquired:", token); } else { console.warn("Failed to acquire notification token"); }
// FCM rotates tokens periodically. Keep the store in sync so the // registration effect re-runs and re-registers the new token, otherwise // a rotated token never reaches the server until the next cold start. msg.onTokenRefresh((refreshed) => { console.log("Notification token refreshed"); set({ notificationToken: refreshed }); });
// Subscribe to topic(s) if desired msg .subscribeToTopic("live") .then(() => console.log("Subscribed to live!")) .catch((e) => console.warn("Failed to subscribe to live topic", e));
// Foreground message handler msg.onMessage((remoteMessage) => { console.log("Foreground message:", remoteMessage); // Could surface an in-app notification or toast here });
// User tapped a notification while app was in background/foreground msg.onNotificationOpenedApp((remoteMessage) => { console.log( "App opened by notification while in background/foreground:", remoteMessage, ); try { // route to the destination encoded in notification payload const data = remoteMessage?.data; if (data) { get().handleNotification(data as any); } } catch (e) { console.error("handleNotification error", e); } });
// App opened from quit state by a notification try { const initial = await msg.getInitialNotification(); if (initial) { console.log("App opened by notification from closed state:", initial); const data = initial?.data; if (data) { get().handleNotification(data as any); } } } catch (e) { console.warn("getInitialNotification error", e); }
return; } catch (e) { console.log("initPushNotifications error", e); } }, registerNotificationToken: async () => { try { // Ensure configured base URL is present if (typeof process.env.EXPO_PUBLIC_STREAMPLACE_URL !== "string") { console.log("process.env.EXPO_PUBLIC_STREAMPLACE_URL undefined!"); return; }
// The store is flat (slices are spread, not namespaced), so read these // directly off get(). The previous get().platform / get().bluesky access // was always undefined, which meant token was undefined and this threw // before ever registering anything. const { notificationToken, oauthSession } = get();
const token = notificationToken; if (!token) { console.log("No notification token to register yet"); return; }
const body: { token: string; type: string; repoDID?: string } = { token, type: "firebase", };
const did = oauthSession?.did; if (did) { body.repoDID = did; }
try { const res = await fetch( `${process.env.EXPO_PUBLIC_STREAMPLACE_URL}/api/notification`, { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(body), }, ); console.log("registerNotificationToken status:", res.status); } catch (e) { console.log("Error registering notification token:", e); } } catch (e) { console.error("registerNotificationToken error", e); } }, enableWebNotifications: async () => { // web-only; native uses FCM via initPushNotifications return "denied"; }, disableWebNotifications: async () => { // web-only }, webNotificationPermission: () => { return "denied"; },});