Live video on the AT Protocol
1import messaging from "@react-native-firebase/messaging";
2import { openAuthSessionAsync } from "expo-web-browser";
3import { oauthCallback } from "features/bluesky/blueskySlice";
4import { BlueskyState } from "features/bluesky/blueskyTypes";
5import { PermissionsAndroid, Platform } from "react-native";
6import { createAppSlice } from "../../hooks/createSlice";
7import {
8 initialState,
9 PlatformState,
10 RegisterNotificationTokenBody,
11} from "./shared";
12
13const checkApplicationPermission = async () => {
14 if (Platform.OS === "android") {
15 try {
16 await PermissionsAndroid.request(
17 PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
18 );
19 } catch (error) {
20 console.log("error getting notifications ", error);
21 }
22 }
23};
24
25export const platformSlice = createAppSlice({
26 name: "platform",
27 initialState,
28 reducers: (create) => ({
29 handleNotification: create.reducer(
30 (
31 state,
32 action: { payload: { [key: string]: string | object } | undefined },
33 ) => {
34 if (!action.payload) {
35 return state;
36 }
37 if (typeof action.payload.path !== "string") {
38 return state;
39 }
40 return {
41 ...state,
42 notificationDestination: action.payload.path,
43 };
44 },
45 ),
46 clearNotification: create.reducer((state) => {
47 return {
48 ...state,
49 notificationDestination: null,
50 };
51 }),
52 openLoginLink: create.asyncThunk(
53 async (url: string, thunkAPI) => {
54 const res = await openAuthSessionAsync(url);
55 if (res.type === "success") {
56 thunkAPI.dispatch(oauthCallback(res.url));
57 }
58 },
59 {
60 pending: (state) => {
61 state.status = "loading";
62 },
63 fulfilled: (state) => {
64 state.status = "idle";
65 },
66 rejected: (state, { error }) => {
67 state.status = "failed";
68 console.error(error);
69 },
70 },
71 ),
72
73 initPushNotifications: create.asyncThunk(
74 async (_, thunkAPI) => {
75 const msg = messaging();
76 messaging().setBackgroundMessageHandler(async (remoteMessage) => {
77 console.log("Message handled in the background!", remoteMessage);
78 });
79 await checkApplicationPermission();
80 const authorizationStatus = await msg.requestPermission();
81
82 let perms = "";
83
84 if (authorizationStatus === messaging.AuthorizationStatus.AUTHORIZED) {
85 console.log("User has notification permissions enabled.");
86 perms += "authorized";
87 } else if (
88 authorizationStatus === messaging.AuthorizationStatus.PROVISIONAL
89 ) {
90 console.log("User has provisional notification permissions.");
91 perms += "provisional";
92 } else {
93 console.log("User has notification permissions disabled");
94 perms += "disabled";
95 }
96
97 const token = await msg.getToken();
98
99 messaging()
100 .subscribeToTopic("live")
101 .then(() => console.log("Subscribed to live!"));
102
103 messaging().onMessage((remoteMessage) => {
104 console.log("Foreground message:", remoteMessage);
105 // Display the notification to the user
106 });
107 messaging().onNotificationOpenedApp((remoteMessage) => {
108 console.log(
109 "App opened by notification while in foreground:",
110 remoteMessage,
111 );
112 thunkAPI.dispatch(handleNotification(remoteMessage.data));
113 // Handle notification interaction when the app is in the foreground
114 });
115 messaging()
116 .getInitialNotification()
117 .then((remoteMessage) => {
118 if (!remoteMessage) {
119 return;
120 }
121 console.log(
122 "App opened by notification from closed state:",
123 remoteMessage,
124 );
125 thunkAPI.dispatch(handleNotification(remoteMessage.data));
126 });
127
128 return { token };
129 },
130 {
131 pending: (state) => {},
132 fulfilled: (state, { payload }) => {
133 return {
134 ...state,
135 notificationToken: payload.token,
136 };
137 },
138 rejected: (state) => {},
139 },
140 ),
141
142 registerNotificationToken: create.asyncThunk(
143 async (_, thunkAPI) => {
144 if (typeof process.env.EXPO_PUBLIC_STREAMPLACE_URL !== "string") {
145 console.log("process.env.EXPO_PUBLIC_STREAMPLACE_URL undefined!");
146 return;
147 }
148 const { platform, bluesky } = thunkAPI.getState() as {
149 platform: PlatformState;
150 bluesky: BlueskyState;
151 };
152 if (!platform.notificationToken) {
153 throw new Error("No notification token");
154 }
155 const body: RegisterNotificationTokenBody = {
156 token: platform.notificationToken,
157 };
158 const did = bluesky.oauthSession?.did;
159 if (did) {
160 body.repoDID = did;
161 }
162 try {
163 const res = await fetch(
164 `${process.env.EXPO_PUBLIC_STREAMPLACE_URL}/api/notification`,
165 {
166 method: "POST",
167 headers: {
168 "content-type": "application/json",
169 },
170 body: JSON.stringify(body),
171 },
172 );
173 console.log({ status: res.status });
174 } catch (e) {
175 console.log(e);
176 }
177 },
178 {
179 pending: (state) => {},
180 fulfilled: (state) => {},
181 rejected: (state) => {},
182 },
183 ),
184 }),
185
186 selectors: {
187 selectNotificationToken: (platform) => platform.notificationToken,
188 selectNotificationDestination: (platform) =>
189 platform.notificationDestination,
190 },
191});
192
193export const {
194 openLoginLink,
195 initPushNotifications,
196 registerNotificationToken,
197 handleNotification,
198 clearNotification,
199} = platformSlice.actions;
200
201export const { selectNotificationToken, selectNotificationDestination } =
202 platformSlice.selectors;