Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(mobile): consolidated all auth utils #1183

Merged
merged 3 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions apps/mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,20 @@
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-router",
"expo-secure-store"
[
"expo-router"
],
[
"expo-secure-store"
],
[
"expo-font",
{
"fonts": [
"./assets/fonts/CalSans-SemiBold.otf"
]
}
]
],
"extra": {
"router": {
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/app/[site_id]/(tabs)/profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { revokeAsync } from 'expo-auth-session';
import { useContext } from 'react';
import { SiteContext } from '../../_layout';
import * as SecureStore from 'expo-secure-store';
import { clearDefaultSite, DEFAULT_SITE_KEY, deleteAccessToken, getAccessTokenKey, getRevocationEndpoint } from '@lib/auth';

const SCREEN_OPTIONS = {
title: 'Profile',
Expand Down Expand Up @@ -117,11 +118,11 @@ function ListFooterComponent() {
clientId: siteInformation?.client_id || '',
token: tokenParams?.token?.() || ''
}, {
revocationEndpoint: siteInformation?.url + '/api/method/frappe.integrations.oauth2.revoke_token'
revocationEndpoint: getRevocationEndpoint(siteInformation?.url || '')
}).then(result => {
return SecureStore.deleteItemAsync(`${siteInformation?.sitename}-access-token`)
return deleteAccessToken(siteInformation?.sitename || '')
}).then((result) => {
return AsyncStorage.removeItem('default-site')
return clearDefaultSite()
}).then(() => {
router.replace('/landing')
}).catch((error) => {
Expand Down
39 changes: 11 additions & 28 deletions apps/mobile/app/[site_id]/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Text } from "@components/nativewindui/Text";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { createContext, useEffect, useState } from "react";
import { View } from "react-native";
import { SiteInformation } from "../../types/SiteInformation";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from 'expo-secure-store';
import { TokenResponse } from "expo-auth-session";
import { FrappeProvider } from "frappe-react-sdk";
import FullPageLoader from "@components/layout/FullPageLoader";
import { getAccessToken, getSiteFromStorage, getTokenEndpoint, storeAccessToken } from "@lib/auth";

export default function SiteLayout() {

Expand All @@ -26,27 +24,15 @@ export default function SiteLayout() {

let site_info: SiteInformation | null = null

AsyncStorage.getItem('sites')
.then(sites => {
if (!sites) {
router.replace('/landing')

// TODO: Show the user a toast saying that the site is not found

return null
}

const parsedSites: { [key: string]: SiteInformation } = JSON.parse(sites)
const siteInfo = parsedSites[site_id]

getSiteFromStorage(site_id)
.then(siteInfo => {
if (!siteInfo) {
router.replace('/landing')

// TODO: Show the user a toast saying that the site is not found

return null
}

setSiteInfo(siteInfo)
site_info = siteInfo

Expand All @@ -55,7 +41,7 @@ export default function SiteLayout() {
.then((siteInfo: SiteInformation | null) => {
if (!siteInfo) return null

return SecureStore.getItemAsync(`${site_id}-access-token`)
return getAccessToken(siteInfo.sitename)
})
.then(accessToken => {
if (!accessToken) {
Expand All @@ -65,16 +51,18 @@ export default function SiteLayout() {

return null
}
const tokenConfig: TokenResponse = JSON.parse(accessToken)

let tokenResponse = new TokenResponse(tokenConfig)
let tokenResponse = new TokenResponse(accessToken)

if (tokenResponse.shouldRefresh()) {
console.log("Refreshing token")
return tokenResponse.refreshAsync({
clientId: site_info?.client_id || '',
}, {
tokenEndpoint: site_info?.url + '/api/method/frappe.integrations.oauth2.get_token',
tokenEndpoint: getTokenEndpoint(site_info?.url || ''),
}).then(async (tokenResponse) => {
await storeAccessToken(site_info?.sitename || '', tokenResponse)
return tokenResponse
})
} else {
return tokenResponse
Expand All @@ -92,12 +80,7 @@ export default function SiteLayout() {

return <>
<Stack.Screen options={{ headerShown: false }} />
{loading ? <View className="flex-1 justify-center items-center gap-2">

{/* TODO: Change this UI */}
<Text className="text-4xl font-bold">raven</Text>
<Text>Setting up your workspace...</Text>
</View> :
{loading ? <FullPageLoader /> :
<SiteContext.Provider value={siteInfo}>
<FrappeProvider
url={siteInfo?.url}
Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { useAsyncStorage } from '@react-native-async-storage/async-storage';
export default function RootLayout() {

const path = usePathname()
console.log(path)

const { getItem } = useAsyncStorage(`default-site`)
console.log(path)

// On load, check if the user has a site set

Expand Down Expand Up @@ -46,6 +46,7 @@ export default function RootLayout() {
<KeyboardProvider statusBarTranslucent navigationBarTranslucent>
<ThemeProvider value={NAV_THEME[colorScheme]}>
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="landing" />
<Stack.Screen name="[site_id]" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
Expand Down
12 changes: 12 additions & 0 deletions apps/mobile/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Stack } from 'expo-router';
import FullPageLoader from '@components/layout/FullPageLoader';

export default function InitialScreen() {

return (
<>
<Stack.Screen options={{ title: 'Raven' }} />
<FullPageLoader />
</>
);
}
8 changes: 4 additions & 4 deletions apps/mobile/app/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { View } from 'react-native';
import { Text } from '@components/nativewindui/Text';
import AddSite from '@components/features/auth/AddSite';

export default function NotFoundScreen() {
export default function LandingScreen() {
return (
<>
<Stack.Screen options={{ title: 'Sites', headerTitle: 'Sites' }} />
<View className='flex-1 py-8 px-4 gap-3'>
<Text className='text-3xl font-bold'>raven</Text>
<Stack.Screen options={{ title: 'Sites', headerShown: false }} />
<View className='flex-1 justify-center pt-48 px-6 gap-3'>
<Text className='text-5xl font-bold font-cal-sans'>raven</Text>
<View className='h-2' />
<AddSite />
</View>
Expand Down
17 changes: 3 additions & 14 deletions apps/mobile/components/features/auth/AddSite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import { BottomSheetView } from '@gorhom/bottom-sheet'
import { useCallback, useState } from 'react'
import { Alert, View } from 'react-native'
import * as WebBrowser from 'expo-web-browser'
import * as SecureStore from 'expo-secure-store';
import { CodeChallengeMethod, exchangeCodeAsync, makeRedirectUri, ResponseType, TokenResponse, useAuthRequest } from 'expo-auth-session';
import AsyncStorage from '@react-native-async-storage/async-storage'
import { router } from 'expo-router'
import { SiteInformation } from '../../../types/SiteInformation'
import { addSiteToStorage, discovery, setDefaultSite, storeAccessToken } from '@lib/auth'

WebBrowser.maybeCompleteAuthSession();

Expand Down Expand Up @@ -87,12 +86,6 @@ const AddSite = (props: Props) => {
)
}

const discovery = {
authorizationEndpoint: '/api/method/frappe.integrations.oauth2.authorize',
tokenEndpoint: '/api/method/frappe.integrations.oauth2.get_token',
revocationEndpoint: '/api/method/frappe.integrations.oauth2.revoke_token',
}

const SiteAuthFlowSheet = ({ siteInformation, onDismiss }: { siteInformation: SiteInformation, onDismiss: () => void }) => {

const discoveryWithURL = {
Expand Down Expand Up @@ -141,8 +134,8 @@ const SiteAuthFlowSheet = ({ siteInformation, onDismiss }: { siteInformation: Si
// 3. Redirect the user to the /[sitename] route

storeAccessToken(siteInformation.sitename, token)
.then(() => AsyncStorage.mergeItem('sites', JSON.stringify({ [siteInformation.sitename]: siteInformation })))
.then(() => AsyncStorage.setItem(`default-site`, siteInformation.sitename))
.then(() => addSiteToStorage(siteInformation.sitename, siteInformation))
.then(() => setDefaultSite(siteInformation.sitename))
.then(() => router.replace(`/${siteInformation.sitename}`))
.then(() => onDismiss())
}
Expand All @@ -163,8 +156,4 @@ const SiteAuthFlowSheet = ({ siteInformation, onDismiss }: { siteInformation: Si
</View>
}

const storeAccessToken = (siteName: string, token: TokenResponse) => {
return SecureStore.setItemAsync(`${siteName}-access-token`, JSON.stringify(token))
}

export default AddSite
19 changes: 19 additions & 0 deletions apps/mobile/components/layout/FullPageLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Text } from '@components/nativewindui/Text'
import React from 'react'
import { View } from 'react-native'

type Props = {
title?: string
description?: string
}

const FullPageLoader = ({ title = 'raven', description = 'Setting up your workspace...' }: Props) => {
return (
<View className="flex-1 justify-center items-center gap-2">
<Text className="text-4xl font-bold font-cal-sans">{title}</Text>
<Text className='text-muted-foreground'>{description}</Text>
</View>
)
}

export default FullPageLoader
Loading
Loading