import React, { useContext, createContext, useReducer, Provider, useCallback, useEffect, useMemo, Suspense } from "react" import { Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { encrypt, decrypt } from './encryption' import env from "../env"; import { flash } from "./toast"; const Login = React.lazy(() => import('./login')) const SignUp = React.lazy(() => import('./signup')) const Home = React.lazy(() => import('./home')) const ResetPassword = React.lazy(() => import('./reset-password')) const jsonRegex = /^application\/json/ const textRegex = /^text\/plain/ const request = async (url: string, method: string, body: any, token: string | null, key?: string, iv?: string, stream?: boolean) => { const headers = new Headers(); headers.append('device-id', localStorage.getItem('device_id') || '') if (token) { headers.append('Authorization', `Bearer ${token}`); if (method !== 'GET') { headers.append('Content-Type', key && iv ? 'text/plain' : 'application/json'); if (key && iv) body = `~e~${encrypt(typeof body === 'object' ? JSON.stringify(body) : body, key, iv)}`; else if (typeof body === 'object') body = JSON.stringify(body); } } else if (method !== 'GET') { headers.append('Content-Type', 'application/json'); body = typeof body === 'object' ? JSON.stringify(body) : body; } return fetch(env.API_HOST + url, { method, headers, body }).then(async response => { if (!response.ok) throw new Error(response.statusText) if (textRegex.test(response.headers.get('content-type') || '')) { return response.text().then(text => { if (text.startsWith('~e~') && key && iv) { let o = null try { o = decrypt(text.slice(3), key, iv) } catch { o = null } if (o) { try { return JSON.parse(o) } catch { return o } } return text } return text }) } else if (jsonRegex.test(response.headers.get('content-type') || '')) { return response.json() } else if (response.body && stream) { return response.body } else { return response } }) } const AppContext = createContext(null); export const useAppContext = () => useContext(AppContext); type AppState = { token: string | null, error: string | null, loading: boolean, key: string | null, iv: string | null } const AppProvider: Provider = AppContext.Provider export default function App() { const [state, setState] = useReducer((state: AppState, newState: AppState) => ({ ...state, ...newState }), { token: null, key: null, iv: null, error: null, loading: false }) as any const location = useLocation() const { pathname, search } = location const queries = useMemo(() => { const q = new URLSearchParams(search) const o: {[key: string]: string} = {} q.forEach((v, k) => o[k] = v) return o }, [search]) const redirect = useNavigate() const encryptData = useCallback((data: string | object) => { if (!state.key || !state.iv) throw new Error('Key or IV not set') const text = typeof data === 'object' ? JSON.stringify(data) : data return encrypt(text, state.key, state.iv) }, [state.key, state.iv]) const decryptData = useCallback((data: string) => { if (!state.key || !state.iv) throw new Error('Key or IV not set') return decrypt(data, state.key, state.iv) }, [state.key, state.iv]) const requestData = useCallback((url: string, method: string, body: any, useAuth: boolean = false) => { if (useAuth && !state.token) throw new Error('Please login first') return request(url, method, body, useAuth ? state.token : null, state.key, state.iv) }, [state.token, state.key, state.iv]) const logout = useCallback(() => { requestData('/invalidate', 'POST', {}, true).catch(() => { }) localStorage.removeItem('token') setState({ iv: null, token: null, key: null }) }, [setState, requestData]) useEffect(() => { let loggedIn = Boolean(state.token) if (!loggedIn) { const tokenInfo = localStorage.getItem('token') if (tokenInfo) { try { const { key, iv, token } = JSON.parse(tokenInfo) if (key && iv && token) { setState({ token, key, iv }) loggedIn = true } } catch { localStorage.removeItem('token') } } } else { localStorage.setItem('token', JSON.stringify({ token: state.token, key: state.key, iv: state.iv })) } if (!loggedIn && pathname !== '/login' && pathname !== '/sign-up' && pathname !== '/reset-password') { redirect('/login') } else if (loggedIn && (pathname === '/login' || pathname === '/sign-up' || pathname === '/reset-password')) { redirect('/') } }, [state.token, state.key, state.iv, pathname]) return ( Loading...}> } /> } /> } /> } /> ) }