using sqlite storage to persist app state

This commit is contained in:
Roland Osborne 2022-09-12 15:18:27 -07:00
parent b23fed9726
commit aeffbf1063
13 changed files with 22372 additions and 952 deletions

View File

@ -5,23 +5,26 @@ import { Root } from 'src/root/Root';
import { Access } from 'src/access/Access';
import { Session } from 'src/session/Session';
import { Admin } from 'src/admin/Admin';
import { StoreContextProvider } from 'context/StoreContext';
import { AppContextProvider } from 'context/AppContext';
export default function App() {
return (
<AppContextProvider>
<NativeRouter>
<Routes>
<Route path="/" element={ <Root /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/login" element={ <Access mode="login" /> } />
<Route path="/reset" element={ <Access mode="reset" /> } />
<Route path="/create" element={ <Access mode="create" /> } />
<Route path="/session" element={ <Session/> } />
</Routes>
</NativeRouter>
</AppContextProvider>
<StoreContextProvider>
<AppContextProvider>
<NativeRouter>
<Routes>
<Route path="/" element={ <Root /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/login" element={ <Access mode="login" /> } />
<Route path="/reset" element={ <Access mode="reset" /> } />
<Route path="/create" element={ <Access mode="create" /> } />
<Route path="/session" element={ <Session/> } />
</Routes>
</NativeRouter>
</AppContextProvider>
</StoreContextProvider>
);
}

View File

@ -238,6 +238,8 @@ PODS:
- React-jsinspector (0.69.5)
- React-logger (0.69.5):
- glog
- react-native-sqlite-storage (6.0.1):
- React-Core
- React-perflogger (0.69.5)
- React-RCTActionSheet (0.69.5):
- React-Core/RCTActionSheetHeaders (= 0.69.5)
@ -335,6 +337,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@ -408,6 +411,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-sqlite-storage:
:path: "../node_modules/react-native-sqlite-storage"
React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-RCTActionSheet:
@ -464,6 +469,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: e42f0b46de293a026c2fb20e524d4fe09f81f575
React-jsinspector: e385fb7a1440ae3f3b2cd1a139ca5aadaab43c10
React-logger: 15c734997c06fe9c9b88e528fb7757601e7a56df
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
React-perflogger: 367418425c5e4a9f0f80385ee1eaacd2a7348f8e
React-RCTActionSheet: e4885e7136f98ded1137cd3daccc05eaed97d5a6
React-RCTAnimation: 7c5a74f301c9b763343ba98a3dd776ed2676993f

21233
app/mobile/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
"react-dom": "18.0.0",
"react-native": "0.69.5",
"react-native-base64": "^0.2.1",
"react-native-sqlite-storage": "^6.0.1",
"react-native-web": "~0.18.7",
"react-router-dom": "6",
"react-router-native": "^6.3.0",

View File

@ -38,6 +38,12 @@ export function useCreate() {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
if (app.state.session) {
navigate('/session');
}
}, [app.state.session]);
useEffect(() => {
if (state.usernameChecked && state.serverChecked && state.tokenChecked &&
state.password && state.username && state.server && state.confirm &&

View File

@ -20,6 +20,12 @@ export function useLogin() {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
if (app.state.session) {
navigate('/session');
}
}, [app.state.session]);
useEffect(() => {
if (state.password && state.login && !state.enabled && state.login.includes('@')) {
updateState({ enabled: true });
@ -56,7 +62,6 @@ export function useLogin() {
updateState({ busy: true });
try {
await app.actions.login(state.login, state.password);
navigate('/');
}
catch (err) {
console.log(err);

View File

@ -19,6 +19,12 @@ export function useReset() {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
if (app.state.session) {
navigate('/session');
}
}, [app.state.session]);
useEffect(() => {
if (state.token && state.server && !state.enabled) {
updateState({ enabled: true });

View File

@ -0,0 +1,14 @@
import { createContext } from 'react';
import { useStoreContext } from './useStoreContext.hook';
export const StoreContext = createContext({});
export function StoreContextProvider({ children }) {
const { state, actions } = useStoreContext();
return (
<StoreContext.Provider value={{ state, actions }}>
{children}
</StoreContext.Provider>
);
}

View File

@ -4,15 +4,15 @@ import { setLogin } from 'api/setLogin';
import { setAccountAccess } from 'api/setAccountAccess';
import { addAccount } from 'api/addAccount';
import { getUsername } from 'api/getUsername';
import { StoreContext } from 'context/StoreContext';
export function useAppContext() {
const [state, setState] = useState({
session: null,
disconnected: null,
server: null,
token: null,
});
const [appRevision, setAppRevision] = useState();
const store = useContext(StoreContext);
const delay = useRef(2);
const ws = useRef(null);
@ -44,33 +44,26 @@ export function useAppContext() {
const appCreate = async (username, password, token) => {
const acc = username.split('@');
await addAccount(acc[0], acc[1], password, token);
let access = await setLogin(acc[0], acc[1], password)
setWebsocket(acc[1], access.appToken)
updateState({ session: true, token: access.appToken, server: acc[1] });
// store
const access = await setLogin(acc[0], acc[1], password)
store.actions.setSession({ ...access, server: acc[1] });
}
const appLogin = async (username, password) => {
const acc = username.split('@');
let access = await setLogin(acc[0], acc[1], password)
console.log(access);
setWebsocket(acc[1], access.appToken)
updateState({ session: true, token: access.appToken, server: acc[1] });
// store
const access = await setLogin(acc[0], acc[1], password)
store.actions.setSession({ ...access, server: acc[1] });
}
const appLogout = () => {
clearWebsocket();
updateState({ session: false, });
// store
store.actions.clearSession();
}
const setWebsocket = (server, token) => {
clearWebsocket();
ws.current = new WebSocket(`wss://${server}/status`);
ws.current.onmessage = (ev) => {
try {
let rev = JSON.parse(ev.data);
const rev = JSON.parse(ev.data);
setAppRevision(rev);
updateState({ disconnected: false });
}
@ -104,15 +97,26 @@ console.log(access);
}
const clearWebsocket = () => {
ws.current.onclose = () => {}
ws.current.close()
ws.current = null
if (ws.current) {
ws.current.onclose = () => {}
ws.current.close()
ws.current = null
}
}
useEffect(() => {
updateState({ session: false });
// pull store set websocket
}, []);
if (store.state.init) {
if (store.state.session) {
const { server, appToken } = store.state.session;
setWebsocket(server, appToken);
updateState({ session: true });
}
else {
clearWebsocket();
updateState({ session: false });
}
}
}, [store.state.session, store.state.init]);
return { state, actions }
}

View File

@ -0,0 +1,129 @@
import { useEffect, useState, useRef, useContext } from 'react';
import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'databag_v001.db';
export function useStoreContext() {
const [state, setState] = useState({
init: false,
session: null,
revision: null,
});
const db = useRef(null);
const loaded = useRef(false);
const syncing = useRef(false);
const setRevision = useRef(null);
const appRevision = useRef({});
const appSession = useRef(null);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }))
}
const setAppRevision = async () => {
if (syncing.current) {
return;
}
if (!setRevision.current) {
return;
}
if (!loaded.current) {
return;
}
if (!appSession.current) {
return;
}
// sync revisions
syncing.current = true;
const rev = setRevision.current;
try {
const id = `${appSession.current.guid}_revision`;
await db.current.executeSql(`UPDATE app SET value=? WHERE key='${id}';`, [encodeObject(rev)]);
appRevision.current = setRevision.current;
}
catch (err) {
console.log(err);
}
syncing.current = false;
};
const initialize = async () => {
SQLite.DEBUG(false);
SQLite.enablePromise(true);
db.current = await SQLite.openDatabase({ name: DATABAG_DB, location: "default" });
await db.current.executeSql("CREATE TABLE IF NOT EXISTS app (key text, value text, unique(key));");
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values ('session', null);");
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values ('revision', null);");
appSession.current = await getAppValue(db.current, 'session');
if (appSession.current) {
const revisionId = `${appSession.current.guid}_revision`;
appRevision.currrent = await getAppValue(db.current, revisionId);
}
loaded.current = true;
updateState({ init: true, session: appSession.current, revision: appRevision.current });
};
const actions = {
setSession: async (access) => {
await db.current.executeSql("UPDATE app SET value=? WHERE key='session';", [encodeObject(access)]);
const revisionId = `${access.guid}_revision`;
appRevision.currrent = await getAppValue(db.current, revisionId);
appSession.current = access;
updateState({ session: access, revision: appRevision.current });
},
clearSession: async () => {
await db.current.executeSql("UPDATE app set value=? WHERE key='session';", [null]);
appSession.current = null;
updateState({ session: null });
},
setRevision: (rev) => {
setRevision.current = rev;
setAppRevision();
},
}
useEffect(() => {
initialize();
}, []);
return { state, actions }
}
function decodeObject(s: string) {
if(s == null) {
return null;
}
return JSON.parse(s);
}
function encodeObject(o: any) {
if(o == null) {
return null;
}
return JSON.stringify(o);
}
function hasResult(res) {
if(res === undefined || res[0] === undefined || res[0].rows === undefined || res[0].rows.length == 0) {
return false;
}
return true;
}
async function getAppValue(sql: SQLite.SQLiteDatabase, id: string) {
const res = await sql.executeSql(`SELECT * FROM app WHERE key='${id}';`);
if (hasResult(res)) {
return decodeObject(res[0].rows.item(0).value);
}
return null;
}

View File

@ -13,14 +13,13 @@ export function useRoot() {
}
useEffect(() => {
if (app.state.session === true) {
navigate('/session');
}
if (app.state.session === false) {
navigate('/login');
}
}, [app]);
}, [app.state]);
const actions = {
};

View File

@ -1,4 +1,13 @@
import { TouchableOpacity, Text } from 'react-native';
import { useContext } from 'react';
import { AppContext } from 'context/AppContext';
export function Session() {
return <></>
const app = useContext(AppContext);
return (
<TouchableOpacity style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }} onPress={app.actions.logout}><Text>LOGOUT</Text></TouchableOpacity>
);
}

File diff suppressed because it is too large Load Diff