diff --git a/app/_layout.tsx b/app/_layout.tsx index cf81362..1007fb2 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -16,7 +16,7 @@ import Home from "."; import { GestureHandlerRootView } from "react-native-gesture-handler"; import AsyncStorage from "@react-native-async-storage/async-storage"; import getHeaders from "happy-headers"; -import { Friends, FullUser, User } from "@/sdk"; +import { checkToken, Friends, FullUser, User } from "@/sdk"; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); @@ -43,64 +43,11 @@ export const ProfileContext = createContext({ setFriends: (friends: Friends | null) => {}, }); -async function checkToken() { - const authToken = await AsyncStorage.getItem("authToken"); - const refreshToken = await AsyncStorage.getItem("refreshToken"); - const userResponse = await fetch( - "https://mobile.bereal.com/api/feeds/friends-v1", - { - headers: { - ...getHeaders(), - Authorization: `Bearer ${authToken}`, - }, - } - ); - if (!userResponse.ok || (await userResponse.json())["_h"] == 0) { - console.log("User seems broken"); - const data = await fetch( - "https://auth.bereal.team/token?grant_type=refresh_token", - { - method: "POST", - headers: { - ...getHeaders(), - "content-type": "application/json", - accept: "*/*", - "x-client-version": "iOS/FirebaseSDK/8.15.0/FirebaseCore-iOS", - "x-firebase-client-log-type": "0", - "x-ios-bundle-identifier": "AlexisBarreyat.BeReal", - "accept-language": "en", - "user-agent": - "FirebaseAuth.iOS/8.15.0 AlexisBarreyat.BeReal/0.22.4 iPhone/14.7.1 hw/iPhone9_1", - }, - body: JSON.stringify({ - client_id: "ios", - client_secret: "962D357B-B134-4AB6-8F53-BEA2B7255420", - refresh_token: refreshToken, - grant_type: "refresh_token", - }), - } - ); - const tokenResponse = await data.json(); - console.log(data); - if (!data.ok) { - console.log("Token seems broken"); - return null; - } - console.log("Token has been refreshed successfully"); - await AsyncStorage.setItem("authToken", tokenResponse.access_token); - await AsyncStorage.setItem("refreshToken", tokenResponse.refresh_token); - router.navigate("/profile"); - return [tokenResponse.access_token, tokenResponse.refresh_token]; - } - return [authToken, refreshToken]; -} - export default function RootLayout() { const [user, setUser] = useState(null); const [sessionInfo, setSessionInfo] = useState(null); const [suggestedFriends, setSuggestedFriends] = useState(null); const [friends, setFriends] = useState(null); - const [loaded] = useFonts({ Inter_400Regular, Inter_500Medium, @@ -110,7 +57,10 @@ export default function RootLayout() { useEffect(() => { if (loaded) { - checkToken().then(() => SplashScreen.hideAsync()); + checkToken().then((out) => { + SplashScreen.hideAsync(); + if (out && out[0]) router.push("/"); + }); } }, [loaded]); diff --git a/app/camera.tsx b/app/camera.tsx index 9738e28..16fafac 100644 --- a/app/camera.tsx +++ b/app/camera.tsx @@ -7,18 +7,20 @@ import { useCameraPermissions, CameraPictureOptions, } from "expo-camera"; -import { useRef, useState } from "react"; +import { useContext, useRef, useState } from "react"; import { CaptureButton } from "@/components/CameraButton"; import { finalizeUpload, generateUploadUrl } from "@/sdk"; import getHeaders from "happy-headers"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { router } from "expo-router"; +import { ProfileContext } from "./_layout"; export default function Camera() { const [facing, setFacing] = useState("back"); const [permission, requestPermission] = useCameraPermissions(); const ref = useRef(null); const [cameraReady, setCameraReady] = useState(false); + const userContext = useContext(ProfileContext); function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/app/index.tsx b/app/index.tsx index e9e4dc7..a2df342 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -7,18 +7,27 @@ import { CameraButton } from "@/components/CameraButton"; import { router } from "expo-router"; import AsyncStorage from "@react-native-async-storage/async-storage"; import getHeaders from "happy-headers"; -import { getFriendsPosts, getMyPosts, UserPost } from "@/sdk"; +import { + checkToken, + FullUser, + getFriendsPosts, + getMyPosts, + getMyProfile, + UserPost, +} from "@/sdk"; import { ScrollView } from "react-native-gesture-handler"; +import { ProfileContext } from "./_layout"; export default function App() { const [visibleItems, setVisibleItems] = useState<{ [key: string]: boolean }>( {} ); // State to check if the component is mounted - const [isMounted, setIsMounted] = useState(false); + const [isUsable, setIsUsable] = useState(false); const [friendsPosts, setFriendsPosts] = useState([]); const [myPosts, setMyPosts] = useState([]); - + const [me, setMe] = useState(null); + const userContext = useContext(ProfileContext); async function checkAuth() { if ( !(await AsyncStorage.getItem("authToken")) || @@ -30,14 +39,22 @@ export default function App() { } // Use useEffect to check if the component is mounted + checkToken().then(() => { + setIsUsable(true); + }); useEffect(() => { - setIsMounted(true); - if (isMounted) { - checkAuth(); - getFriendsPosts().then((p) => setFriendsPosts(p)); - getMyPosts().then((p) => setMyPosts(p)); + if (isUsable) { + setMyPosts([]); + setFriendsPosts([]); + setMe(null); + if (isUsable) { + checkAuth(); + getFriendsPosts().then((p) => setFriendsPosts(p)); + getMyPosts().then((p) => setMyPosts(p)); + getMyProfile(userContext).then((p) => setMe(p)); + } } - }, [isMounted]); + }, [isUsable]); const handleVisibilityChange = (id: string, isVisible: boolean) => { setVisibleItems((prevState) => ({ @@ -48,7 +65,13 @@ export default function App() { return ( - + { + getFriendsPosts().then((p) => setFriendsPosts(p)); + getMyPosts().then((p) => setMyPosts(p)); + getMyProfile(userContext).then((p) => setMe(p)); + }} + /> ))} - {friendsPosts.map((value) => ( - handleVisibilityChange(value.id, inView)} - > - - - ))} - + + {friendsPosts.map((value) => ( + handleVisibilityChange(value.id, inView)} + > + + + ))} + + - + ); } @@ -123,11 +148,9 @@ const styles = StyleSheet.create({ }, scrollView: { position: "static", - top: -80, - paddingTop: 80, + top: 0, left: 0, width: "100%", - marginBottom: 10, }, container: { flex: 1, // Fill the available space diff --git a/components/CameraButton.tsx b/components/CameraButton.tsx index c865caf..d5eef15 100644 --- a/components/CameraButton.tsx +++ b/components/CameraButton.tsx @@ -1,9 +1,9 @@ import { Link } from "expo-router"; import { Pressable, View, StyleSheet, TouchableOpacity } from "react-native"; -export function CameraButton() { +export function CameraButton({ shown }: { shown: boolean }) { return ( - + diff --git a/components/TopBar.tsx b/components/TopBar.tsx index dd66752..c90aeb2 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -1,12 +1,20 @@ import { ProfileContext, ProfileContextType } from "@/app/_layout"; import { getMyProfile } from "@/sdk"; import { MaterialIcons } from "@expo/vector-icons"; -import { Link } from "expo-router"; +import { Link, router } from "expo-router"; import getHeaders from "happy-headers"; import { useContext, useEffect, useState } from "react"; import { View, Pressable, Text, Image, StyleSheet } from "react-native"; -export const TopBar = ({ small = false }: { small?: boolean }) => { +export const TopBar = ({ + small = false, + action = () => { + router.navigate("/"); + }, +}: { + small?: boolean; + action?: () => void; +}) => { const userContext = useContext(ProfileContext); const [profilePicture, setProfilePicture] = useState("rebeal://profile"); @@ -28,9 +36,9 @@ export const TopBar = ({ small = false }: { small?: boolean }) => { )} - + action()}> ReBeal. - + {!small && ( diff --git a/sdk.ts b/sdk.ts index fe215da..b3c1186 100644 --- a/sdk.ts +++ b/sdk.ts @@ -2,6 +2,7 @@ import getHeaders from "happy-headers"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useContext } from "react"; import { ProfileContext, ProfileContextType } from "./app/_layout"; +import { router } from "expo-router"; // Generated by https://quicktype.io @@ -518,4 +519,55 @@ export async function finalizeUpload(data: any) { } else { throw new Error("Error uploading image"); } -} \ No newline at end of file +} + +export async function checkToken() { + const authToken = await AsyncStorage.getItem("authToken"); + const refreshToken = await AsyncStorage.getItem("refreshToken"); + const userResponse = await fetch( + "https://mobile.bereal.com/api/feeds/friends-v1", + { + headers: { + ...getHeaders(), + Authorization: `Bearer ${authToken}`, + }, + } + ); + if (!userResponse.ok || (await userResponse.json())["_h"] == 0) { + console.log("User seems broken"); + const data = await fetch( + "https://auth.bereal.team/token?grant_type=refresh_token", + { + method: "POST", + headers: { + ...getHeaders(), + "content-type": "application/json", + accept: "*/*", + "x-client-version": "iOS/FirebaseSDK/8.15.0/FirebaseCore-iOS", + "x-firebase-client-log-type": "0", + "x-ios-bundle-identifier": "AlexisBarreyat.BeReal", + "accept-language": "en", + "user-agent": + "FirebaseAuth.iOS/8.15.0 AlexisBarreyat.BeReal/0.22.4 iPhone/14.7.1 hw/iPhone9_1", + }, + body: JSON.stringify({ + client_id: "ios", + client_secret: "962D357B-B134-4AB6-8F53-BEA2B7255420", + refresh_token: refreshToken, + grant_type: "refresh_token", + }), + } + ); + const tokenResponse = await data.json(); + console.log(data); + if (!data.ok) { + console.log("Token seems broken"); + return null; + } + console.log("Token has been refreshed successfully"); + await AsyncStorage.setItem("authToken", tokenResponse.access_token); + await AsyncStorage.setItem("refreshToken", tokenResponse.refresh_token); + return [true, tokenResponse.access_token, tokenResponse.refresh_token]; + } + return [false, authToken, refreshToken]; + } \ No newline at end of file