Stockage persistent des requêtes
This commit is contained in:
parent
1c52ff5a10
commit
db7a0b970d
@ -3,7 +3,10 @@ import { Stack, useNavigationContainerRef } from 'expo-router'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { Provider as StoreProvider } from 'react-redux'
|
||||
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
||||
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
|
||||
import { useReactNavigationDevTools } from '@dev-plugins/react-navigation'
|
||||
import { useReactQueryDevTools } from '@dev-plugins/react-query'
|
||||
import { useColorScheme } from '@/hooks/useColorScheme'
|
||||
@ -12,7 +15,15 @@ import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
|
||||
import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
|
||||
import LoginProvider from '@/components/LoginProvider'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 24 * 60 * 60 * 1000, // 24h
|
||||
staleTime: 2000,
|
||||
retry: 5,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default function RootLayout() {
|
||||
useStartGeolocationServiceEffect()
|
||||
@ -21,12 +32,18 @@ export default function RootLayout() {
|
||||
|
||||
const navigationRef = useNavigationContainerRef()
|
||||
useReactNavigationDevTools(navigationRef)
|
||||
|
||||
useReactQueryDevTools(queryClient)
|
||||
|
||||
const asyncStoragePersister = createAsyncStoragePersister({
|
||||
storage: AsyncStorage,
|
||||
})
|
||||
|
||||
return (
|
||||
<StoreProvider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={{ persister: asyncStoragePersister }}
|
||||
onSuccess={() => queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
|
||||
<LoginProvider loginRedirect={'/login'}>
|
||||
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
|
||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
@ -39,7 +56,7 @@ export default function RootLayout() {
|
||||
</ThemeProvider>
|
||||
</PaperProvider>
|
||||
</LoginProvider>
|
||||
</QueryClientProvider>
|
||||
</PersistQueryClientProvider>
|
||||
</StoreProvider>
|
||||
)
|
||||
}
|
||||
|
@ -25,8 +25,23 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
|
||||
}
|
||||
})
|
||||
|
||||
// Renouvellement auto du jeton d'authentification
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const storedName = await SecureStore.getItemAsync('apiName')
|
||||
const storedToken = await SecureStore.getItemAsync('apiToken')
|
||||
if (!auth.loggedIn && storedName !== null && storedName !== auth.name && storedToken !== auth.token) {
|
||||
authLogin({ name: storedName, token: storedToken })
|
||||
return
|
||||
}
|
||||
|
||||
// Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
|
||||
if (!auth.loggedIn && route.pathname !== loginRedirect)
|
||||
router.navigate(loginRedirect)
|
||||
})()
|
||||
}, [auth, authLogin, router, route])
|
||||
|
||||
useEffect(() => {
|
||||
// Renouvellement auto du jeton d'authentification
|
||||
const { name, token } = auth
|
||||
const password = SecureStore.getItem('apiPassword')
|
||||
if (name === null || (password === null && token === null))
|
||||
@ -45,13 +60,7 @@ export default function LoginProvider({ loginRedirect, children }: Props) {
|
||||
loginMutation.mutate({ name, password })
|
||||
}, waitTime)
|
||||
return () => clearTimeout(timeout)
|
||||
}, [auth])
|
||||
|
||||
// Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
|
||||
useEffect(() => {
|
||||
if (!auth.loggedIn && route.pathname !== loginRedirect)
|
||||
router.navigate(loginRedirect)
|
||||
}, [auth, route, router])
|
||||
}, [auth, authLogin])
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
@ -24,14 +24,15 @@ type LoginProps = {
|
||||
|
||||
export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginProps) => {
|
||||
return useMutation({
|
||||
mutationFn: ({ name, password }: LoginForm) => {
|
||||
mutationFn: async ({ name, password }: LoginForm) => {
|
||||
return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/auth/login/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: name, password: password })
|
||||
}).then(resp => resp.json())
|
||||
},
|
||||
onSuccess: (data, { name, password }: LoginForm) => {
|
||||
networkMode: 'always',
|
||||
onSuccess: async (data, { name, password }: LoginForm) => {
|
||||
if (data.error) {
|
||||
if (onError)
|
||||
onError({ response: data })
|
||||
@ -41,7 +42,7 @@ export const useLoginMutation = ({ authLogin, onPostSuccess, onError }: LoginPro
|
||||
if (onPostSuccess)
|
||||
onPostSuccess()
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
onError: async (error: Error) => {
|
||||
if (onError)
|
||||
onError({ error: error })
|
||||
}
|
||||
|
79
client/package-lock.json
generated
79
client/package-lock.json
generated
@ -13,10 +13,13 @@
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
|
||||
"@pchmn/expo-material3-theme": "github:pchmn/expo-material3-theme",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"@reduxjs/toolkit": "^2.4.0",
|
||||
"@tanstack/query-async-storage-persister": "^5.62.7",
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"@tanstack/react-query-persist-client": "^5.62.7",
|
||||
"@turf/circle": "^7.1.0",
|
||||
"expo": "~52.0.11",
|
||||
"expo-background-fetch": "~13.0.3",
|
||||
@ -3846,6 +3849,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.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.3.tgz",
|
||||
@ -4507,6 +4522,19 @@
|
||||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-async-storage-persister": {
|
||||
"version": "5.62.7",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.62.7.tgz",
|
||||
"integrity": "sha512-8qSJ1oTnGhCikPADWd35xrswyYtmkqYnakWgqeXjxL+F+qPGgsfexNUlBu9TNqo9eAP/1ia4Lt5Ks2fTsMzBgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-persist-client-core": "5.62.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.62.7",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
|
||||
@ -4517,6 +4545,19 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-persist-client-core": {
|
||||
"version": "5.62.7",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.62.7.tgz",
|
||||
"integrity": "sha512-9HcaD9rEp2nGWnrw2osK5UCSKJbJKEdn+MEhVVfnUPSFN7MZFpFZxpRCHJi3fRpWOYsVeH9EFODX+aoJaniJMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.62.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.62.7",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
|
||||
@ -4533,6 +4574,23 @@
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query-persist-client": {
|
||||
"version": "5.62.7",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.62.7.tgz",
|
||||
"integrity": "sha512-RmEJ3YvsK7lv1Of3CCXBgHDtoZVwHMtTKCTegZz+xijVJsgJaNNfel4YTpbQ0ydnWT2IcohdqnHUtBE6p1KCIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-persist-client-core": "5.62.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@ -9472,6 +9530,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",
|
||||
@ -11406,6 +11473,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",
|
||||
|
@ -22,7 +22,9 @@
|
||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"@reduxjs/toolkit": "^2.4.0",
|
||||
"@tanstack/query-async-storage-persister": "^5.62.7",
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"@tanstack/react-query-persist-client": "^5.62.7",
|
||||
"@turf/circle": "^7.1.0",
|
||||
"expo": "~52.0.11",
|
||||
"expo-background-fetch": "~13.0.3",
|
||||
@ -56,7 +58,8 @@
|
||||
"react-native-screens": "~4.1.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.2",
|
||||
"react-redux": "^9.1.2"
|
||||
"react-redux": "^9.1.2",
|
||||
"@react-native-async-storage/async-storage": "1.23.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
@ -17,3 +17,27 @@ export function setItem(key: string, value: string): void {
|
||||
export async function setItemAsync(key: string, value: string): Promise<void> {
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
export async function deleteItemAsync(key: string): Promise<void> {
|
||||
return AsyncStorage.removeItem(key)
|
||||
}
|
||||
|
||||
export function getItem(key: string): string | null {
|
||||
return Promise.apply(AsyncStorage.getItem(key))
|
||||
}
|
||||
|
||||
export async function getItemAsync(key: string): Promise<string | null> {
|
||||
return AsyncStorage.getItem(key)
|
||||
}
|
||||
|
||||
export function setItem(key: string, value: string): void {
|
||||
AsyncStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
export async function setItemAsync(key: string, value: string): Promise<void> {
|
||||
return AsyncStorage.setItem(key, value)
|
||||
}
|
||||
*/
|
@ -15,9 +15,9 @@ export interface AuthPayload {
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
loggedIn: SecureStore.getItem('apiName') !== null && SecureStore.getItem('apiToken') !== null,
|
||||
name: SecureStore.getItem('apiName'),
|
||||
token: SecureStore.getItem('apiToken'),
|
||||
loggedIn: false,
|
||||
name: null,
|
||||
token: null,
|
||||
}
|
||||
|
||||
export const authSlice = createSlice({
|
||||
|
Loading…
Reference in New Issue
Block a user