diff --git a/frontend/app/register/index.tsx b/frontend/app/register/index.tsx index 6ca43ee..3360e5e 100644 --- a/frontend/app/register/index.tsx +++ b/frontend/app/register/index.tsx @@ -7,6 +7,10 @@ import { register } from '@/services/auth/register'; import { Button } from '@/components/Button'; import { Spinner } from '@/components/Spinner'; import { useRouter } from 'expo-router'; +import { + getSpotifyCredentials, + validateSpotifyCredentials, +} from '@/utils/spotify'; const Index = () => { const [username, setUsername] = useState(''); @@ -14,9 +18,15 @@ const Index = () => { const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); const registerUser = async () => { + const isSpotifyValid = await validateSpotifyCredentials(); + + if (!isSpotifyValid) { + setError('Please authorize Spotify before continuing'); + return; + } + setError(undefined); setIsLoading(true); const [_, error] = await register(username, password); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5f6db21..078abdb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^14.0.2", + "@react-native-async-storage/async-storage": "1.23.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "expo": "~52.0.28", @@ -2961,6 +2962,18 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz", + "integrity": "sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.76.6", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.6.tgz", @@ -7525,6 +7538,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -9344,6 +9366,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 435f2c2..84346da 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,7 +40,8 @@ "react-native-screens": "~4.4.0", "react-native-web": "~0.19.13", "react-native-webview": "13.12.5", - "expo-secure-store": "~14.0.1" + "expo-secure-store": "~14.0.1", + "@react-native-async-storage/async-storage": "1.23.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/frontend/utils/spotify.ts b/frontend/utils/spotify.ts index 397dd9e..d6db911 100644 --- a/frontend/utils/spotify.ts +++ b/frontend/utils/spotify.ts @@ -1,5 +1,10 @@ import { AccessTokenResponse } from '../types'; import { makeRedirectUri } from 'expo-auth-session'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const ACCESS_TOKEN_KEY = 'spotify-access-token'; +const EXPIRATION_KEY = 'spotify-expiration'; +const REFRESH_TOKEN_KEY = 'spotify-refresh-token'; export const getClientId = () => { return process.env.EXPO_PUBLIC_SPOTIFY_CLIENT_ID; @@ -27,6 +32,11 @@ export const fetchAccessToken = async (code: string) => { }, }); const json = (await response.json()) as AccessTokenResponse; + saveSpotifyCredentials( + json.access_token, + json.expires_in, + json.refresh_token + ); console.log(json); return { accessToken: json.access_token, @@ -34,3 +44,36 @@ export const fetchAccessToken = async (code: string) => { refreshToken: json.refresh_token, }; }; + +export const saveSpotifyCredentials = async ( + accessToken: string, + expiration: number, + refreshToken: string +) => { + await AsyncStorage.setItem(ACCESS_TOKEN_KEY, accessToken); + await AsyncStorage.setItem( + EXPIRATION_KEY, + (Date.now() + expiration).toString() + ); + await AsyncStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); +}; + +export const getSpotifyCredentials = async () => { + const storedExpiration = await AsyncStorage.getItem(EXPIRATION_KEY); + return { + accessToken: await AsyncStorage.getItem(ACCESS_TOKEN_KEY), + expiration: storedExpiration ? parseInt(storedExpiration) : null, + refreshToken: await AsyncStorage.getItem(REFRESH_TOKEN_KEY), + }; +}; + +export const validateSpotifyCredentials = async () => { + const { accessToken, expiration, refreshToken } = + await getSpotifyCredentials(); + + if (!expiration || expiration > Date.now()) { + return false; + } + + return accessToken && refreshToken; +};