mirror of
https://github.com/balzack/databag.git
synced 2025-02-11 19:19:16 +00:00
using sqlite storage to persist app state
This commit is contained in:
parent
b23fed9726
commit
aeffbf1063
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
21233
app/mobile/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
|
@ -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 });
|
||||
|
14
app/mobile/src/context/StoreContext.js
Normal file
14
app/mobile/src/context/StoreContext.js
Normal 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>
|
||||
);
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
129
app/mobile/src/context/useStoreContext.hook.js
Normal file
129
app/mobile/src/context/useStoreContext.hook.js
Normal 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;
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
1833
app/mobile/yarn.lock
1833
app/mobile/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user