frontend: introduce a notification observer
Parents:
cb5d2d51 file(s) changed
- frontend/hooks/useNotificationObserver.ts +72 -0
frontend/hooks/useNotificationObserver.ts
@@ -0,0 +1,72 @@
1 + import { useEffect, useRef } from "react";
2 + import * as Notifications from "expo-notifications";
3 + import { useRouter, useRootNavigationState } from "expo-router";
4 +
5 + export function useNotificationObserver() {
6 + const router = useRouter();
7 + const rootNavState = useRootNavigationState();
8 + const routerReady = !!rootNavState?.key;
9 + const handled = useRef(false);
10 + const lastHandledId = useRef<string | null>(null);
11 +
12 + useEffect(() => {
13 + if (!routerReady) return;
14 + if (handled.current) return;
15 +
16 + let isMounted = true;
17 +
18 + function handleResponse(
19 + response: Notifications.NotificationResponse | null,
20 + coldLaunch: boolean,
21 + ) {
22 + if (!response || !isMounted) return;
23 +
24 + const id = response.notification.request.identifier;
25 + const { request } = response.notification;
26 + const { content } = request;
27 + const { title, body, subtitle, data } = content;
28 +
29 + if (lastHandledId.current === id) return;
30 + lastHandledId.current = id;
31 +
32 + // XXX: Looks so dumb, actually
33 + const { screen, ...params } = (data ?? {}) as Record<string, any>;
34 +
35 + if (!screen) return;
36 +
37 + const payload = { body, title, ...params };
38 +
39 + if (coldLaunch) {
40 + router.replace({
41 + pathname: screen as any,
42 + params: payload,
43 + });
44 + } else {
45 + router.push({
46 + pathname: screen as any,
47 + params: payload,
48 + });
49 + }
50 + }
51 +
52 + // Cold launch
53 + Notifications.getLastNotificationResponseAsync().then((response) => {
54 + if (!handled.current) {
55 + handled.current = true;
56 + handleResponse(response, true);
57 + }
58 + });
59 +
60 + // Warm/foreground
61 + const subscription = Notifications.addNotificationResponseReceivedListener(
62 + (response) => {
63 + handleResponse(response, false);
64 + },
65 + );
66 +
67 + return () => {
68 + isMounted = false;
69 + subscription.remove();
70 + };
71 + }, [routerReady, router]);
72 + }