88 useMemo ,
99 useState ,
1010} from 'react' ;
11+ import { AppState } from 'react-native' ;
1112
1213import { api , ApiError , setAccountRestrictionHandler } from '../api/client' ;
1314import type { User } from '../types' ;
@@ -18,7 +19,7 @@ interface SessionValue {
1819 token : string | null ;
1920 user : User | null ;
2021 loading : boolean ;
21- warning : string | null ;
22+ warning : { id : string ; message : string } | null ;
2223 restriction : { code : string ; message : string } | null ;
2324 signIn : ( email : string , password : string ) => Promise < void > ;
2425 signUp : ( input : {
@@ -28,6 +29,7 @@ interface SessionValue {
2829 role : 'commissioner' | 'maker' ;
2930 } ) => Promise < void > ;
3031 signOut : ( ) => Promise < void > ;
32+ dismissWarning : ( ) => Promise < void > ;
3133 dismissRestriction : ( ) => void ;
3234 refresh : ( ) => Promise < void > ;
3335}
@@ -37,7 +39,7 @@ const SessionContext = createContext<SessionValue | null>(null);
3739export function SessionProvider ( { children } : PropsWithChildren ) {
3840 const [ token , setToken ] = useState < string | null > ( null ) ;
3941 const [ user , setUser ] = useState < User | null > ( null ) ;
40- const [ warning , setWarning ] = useState < string | null > ( null ) ;
42+ const [ warning , setWarning ] = useState < { id : string ; message : string } | null > ( null ) ;
4143 const [ restriction , setRestriction ] = useState < { code : string ; message : string } | null > ( null ) ;
4244 const [ loading , setLoading ] = useState ( true ) ;
4345
@@ -54,47 +56,91 @@ export function SessionProvider({ children }: PropsWithChildren) {
5456 void SecureStore . deleteItemAsync ( tokenKey ) ;
5557 setToken ( null ) ;
5658 setUser ( null ) ;
59+ setWarning ( null ) ;
5760 setRestriction ( { code : error . code , message : error . message } ) ;
5861 } ) ;
5962 return ( ) => setAccountRestrictionHandler ( null ) ;
6063 } , [ ] ) ;
6164
62- const restoreSession = useCallback ( async ( ) => {
63- const saved = await SecureStore . getItemAsync ( tokenKey ) ;
64- if ( ! saved ) {
65- setLoading ( false ) ;
66- return ;
67- }
68-
65+ const checkSession = useCallback ( async ( sessionToken : string , clearOnFailure = false ) => {
6966 try {
70- const result = await api . me ( saved ) ;
71- setToken ( saved ) ;
67+ const result = await api . me ( sessionToken ) ;
68+ setToken ( sessionToken ) ;
7269 setUser ( result . user ) ;
73- setWarning ( result . warnings [ 0 ] ?. message ?? null ) ;
70+ setWarning ( result . warnings [ 0 ] ?? null ) ;
7471 } catch ( error ) {
7572 if ( error instanceof ApiError && [ 'ACCOUNT_SUSPENDED' , 'ACCOUNT_DELETED' ] . includes ( error . code ) ) {
7673 setRestriction ( { code : error . code , message : error . message } ) ;
7774 await SecureStore . deleteItemAsync ( tokenKey ) ;
7875 setToken ( null ) ;
7976 setUser ( null ) ;
80- } else {
77+ setWarning ( null ) ;
78+ } else if ( clearOnFailure ) {
8179 await clearSession ( ) ;
8280 }
83- } finally {
84- setLoading ( false ) ;
8581 }
8682 } , [ clearSession ] ) ;
8783
84+ const restoreSession = useCallback ( async ( ) => {
85+ const saved = await SecureStore . getItemAsync ( tokenKey ) ;
86+ if ( saved ) {
87+ await checkSession ( saved , true ) ;
88+ }
89+ setLoading ( false ) ;
90+ } , [ checkSession ] ) ;
91+
8892 useEffect ( ( ) => {
8993 void restoreSession ( ) ;
9094 } , [ restoreSession ] ) ;
9195
96+ useEffect ( ( ) => {
97+ if ( ! token ) return ;
98+
99+ let requestInFlight = false ;
100+ const checkCurrentSession = async ( ) => {
101+ if ( requestInFlight ) return ;
102+ requestInFlight = true ;
103+ try {
104+ await checkSession ( token ) ;
105+ } finally {
106+ requestInFlight = false ;
107+ }
108+ } ;
109+ const interval = setInterval ( ( ) => void checkCurrentSession ( ) , 3_000 ) ;
110+ const subscription = AppState . addEventListener ( 'change' , ( state ) => {
111+ if ( state === 'active' ) void checkCurrentSession ( ) ;
112+ } ) ;
113+
114+ return ( ) => {
115+ clearInterval ( interval ) ;
116+ subscription . remove ( ) ;
117+ } ;
118+ } , [ checkSession , token ] ) ;
119+
92120 const finishAuthentication = useCallback ( async ( result : { token : string ; user : User } ) => {
93121 await SecureStore . setItemAsync ( tokenKey , result . token ) ;
94122 setToken ( result . token ) ;
95123 setUser ( result . user ) ;
96124 setWarning ( null ) ;
97- } , [ ] ) ;
125+ setRestriction ( null ) ;
126+ await checkSession ( result . token ) ;
127+ } , [ checkSession ] ) ;
128+
129+ const dismissWarning = useCallback ( async ( ) => {
130+ if ( ! token || ! warning ) return ;
131+ const currentWarning = warning ;
132+ setWarning ( null ) ;
133+ try {
134+ await api . readWarning ( token , currentWarning . id ) ;
135+ } catch ( error ) {
136+ if (
137+ ! ( error instanceof ApiError ) ||
138+ ! [ 'ACCOUNT_SUSPENDED' , 'ACCOUNT_DELETED' ] . includes ( error . code )
139+ ) {
140+ setWarning ( currentWarning ) ;
141+ }
142+ }
143+ } , [ token , warning ] ) ;
98144
99145 const value = useMemo < SessionValue > (
100146 ( ) => ( {
@@ -106,10 +152,21 @@ export function SessionProvider({ children }: PropsWithChildren) {
106152 signIn : async ( email , password ) => finishAuthentication ( await api . login ( email , password ) ) ,
107153 signUp : async ( input ) => finishAuthentication ( await api . signup ( input ) ) ,
108154 signOut : clearSession ,
155+ dismissWarning,
109156 dismissRestriction : ( ) => setRestriction ( null ) ,
110157 refresh : restoreSession ,
111158 } ) ,
112- [ clearSession , finishAuthentication , loading , restriction , restoreSession , token , user , warning ] ,
159+ [
160+ clearSession ,
161+ dismissWarning ,
162+ finishAuthentication ,
163+ loading ,
164+ restriction ,
165+ restoreSession ,
166+ token ,
167+ user ,
168+ warning ,
169+ ] ,
113170 ) ;
114171
115172 return < SessionContext . Provider value = { value } > { children } </ SessionContext . Provider > ;
0 commit comments