mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29: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 { Access } from 'src/access/Access';
|
||||||
import { Session } from 'src/session/Session';
|
import { Session } from 'src/session/Session';
|
||||||
import { Admin } from 'src/admin/Admin';
|
import { Admin } from 'src/admin/Admin';
|
||||||
|
import { StoreContextProvider } from 'context/StoreContext';
|
||||||
import { AppContextProvider } from 'context/AppContext';
|
import { AppContextProvider } from 'context/AppContext';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContextProvider>
|
<StoreContextProvider>
|
||||||
<NativeRouter>
|
<AppContextProvider>
|
||||||
<Routes>
|
<NativeRouter>
|
||||||
<Route path="/" element={ <Root /> } />
|
<Routes>
|
||||||
<Route path="/admin" element={ <Admin /> } />
|
<Route path="/" element={ <Root /> } />
|
||||||
<Route path="/login" element={ <Access mode="login" /> } />
|
<Route path="/admin" element={ <Admin /> } />
|
||||||
<Route path="/reset" element={ <Access mode="reset" /> } />
|
<Route path="/login" element={ <Access mode="login" /> } />
|
||||||
<Route path="/create" element={ <Access mode="create" /> } />
|
<Route path="/reset" element={ <Access mode="reset" /> } />
|
||||||
<Route path="/session" element={ <Session/> } />
|
<Route path="/create" element={ <Access mode="create" /> } />
|
||||||
</Routes>
|
<Route path="/session" element={ <Session/> } />
|
||||||
</NativeRouter>
|
</Routes>
|
||||||
</AppContextProvider>
|
</NativeRouter>
|
||||||
|
</AppContextProvider>
|
||||||
|
</StoreContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +238,8 @@ PODS:
|
|||||||
- React-jsinspector (0.69.5)
|
- React-jsinspector (0.69.5)
|
||||||
- React-logger (0.69.5):
|
- React-logger (0.69.5):
|
||||||
- glog
|
- glog
|
||||||
|
- react-native-sqlite-storage (6.0.1):
|
||||||
|
- React-Core
|
||||||
- React-perflogger (0.69.5)
|
- React-perflogger (0.69.5)
|
||||||
- React-RCTActionSheet (0.69.5):
|
- React-RCTActionSheet (0.69.5):
|
||||||
- React-Core/RCTActionSheetHeaders (= 0.69.5)
|
- React-Core/RCTActionSheetHeaders (= 0.69.5)
|
||||||
@ -335,6 +337,7 @@ DEPENDENCIES:
|
|||||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
- 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-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||||
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||||
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
||||||
@ -408,6 +411,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
||||||
React-logger:
|
React-logger:
|
||||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||||
|
react-native-sqlite-storage:
|
||||||
|
:path: "../node_modules/react-native-sqlite-storage"
|
||||||
React-perflogger:
|
React-perflogger:
|
||||||
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
|
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
|
||||||
React-RCTActionSheet:
|
React-RCTActionSheet:
|
||||||
@ -464,6 +469,7 @@ SPEC CHECKSUMS:
|
|||||||
React-jsiexecutor: e42f0b46de293a026c2fb20e524d4fe09f81f575
|
React-jsiexecutor: e42f0b46de293a026c2fb20e524d4fe09f81f575
|
||||||
React-jsinspector: e385fb7a1440ae3f3b2cd1a139ca5aadaab43c10
|
React-jsinspector: e385fb7a1440ae3f3b2cd1a139ca5aadaab43c10
|
||||||
React-logger: 15c734997c06fe9c9b88e528fb7757601e7a56df
|
React-logger: 15c734997c06fe9c9b88e528fb7757601e7a56df
|
||||||
|
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
||||||
React-perflogger: 367418425c5e4a9f0f80385ee1eaacd2a7348f8e
|
React-perflogger: 367418425c5e4a9f0f80385ee1eaacd2a7348f8e
|
||||||
React-RCTActionSheet: e4885e7136f98ded1137cd3daccc05eaed97d5a6
|
React-RCTActionSheet: e4885e7136f98ded1137cd3daccc05eaed97d5a6
|
||||||
React-RCTAnimation: 7c5a74f301c9b763343ba98a3dd776ed2676993f
|
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-dom": "18.0.0",
|
||||||
"react-native": "0.69.5",
|
"react-native": "0.69.5",
|
||||||
"react-native-base64": "^0.2.1",
|
"react-native-base64": "^0.2.1",
|
||||||
|
"react-native-sqlite-storage": "^6.0.1",
|
||||||
"react-native-web": "~0.18.7",
|
"react-native-web": "~0.18.7",
|
||||||
"react-router-dom": "6",
|
"react-router-dom": "6",
|
||||||
"react-router-native": "^6.3.0",
|
"react-router-native": "^6.3.0",
|
||||||
|
@ -38,6 +38,12 @@ export function useCreate() {
|
|||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (app.state.session) {
|
||||||
|
navigate('/session');
|
||||||
|
}
|
||||||
|
}, [app.state.session]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.usernameChecked && state.serverChecked && state.tokenChecked &&
|
if (state.usernameChecked && state.serverChecked && state.tokenChecked &&
|
||||||
state.password && state.username && state.server && state.confirm &&
|
state.password && state.username && state.server && state.confirm &&
|
||||||
|
@ -20,6 +20,12 @@ export function useLogin() {
|
|||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (app.state.session) {
|
||||||
|
navigate('/session');
|
||||||
|
}
|
||||||
|
}, [app.state.session]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.password && state.login && !state.enabled && state.login.includes('@')) {
|
if (state.password && state.login && !state.enabled && state.login.includes('@')) {
|
||||||
updateState({ enabled: true });
|
updateState({ enabled: true });
|
||||||
@ -56,7 +62,6 @@ export function useLogin() {
|
|||||||
updateState({ busy: true });
|
updateState({ busy: true });
|
||||||
try {
|
try {
|
||||||
await app.actions.login(state.login, state.password);
|
await app.actions.login(state.login, state.password);
|
||||||
navigate('/');
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -19,6 +19,12 @@ export function useReset() {
|
|||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (app.state.session) {
|
||||||
|
navigate('/session');
|
||||||
|
}
|
||||||
|
}, [app.state.session]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.token && state.server && !state.enabled) {
|
if (state.token && state.server && !state.enabled) {
|
||||||
updateState({ enabled: true });
|
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 { setAccountAccess } from 'api/setAccountAccess';
|
||||||
import { addAccount } from 'api/addAccount';
|
import { addAccount } from 'api/addAccount';
|
||||||
import { getUsername } from 'api/getUsername';
|
import { getUsername } from 'api/getUsername';
|
||||||
|
import { StoreContext } from 'context/StoreContext';
|
||||||
|
|
||||||
export function useAppContext() {
|
export function useAppContext() {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
session: null,
|
session: null,
|
||||||
disconnected: null,
|
disconnected: null,
|
||||||
server: null,
|
|
||||||
token: null,
|
|
||||||
});
|
});
|
||||||
const [appRevision, setAppRevision] = useState();
|
const [appRevision, setAppRevision] = useState();
|
||||||
|
const store = useContext(StoreContext);
|
||||||
|
|
||||||
const delay = useRef(2);
|
const delay = useRef(2);
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
@ -44,33 +44,26 @@ export function useAppContext() {
|
|||||||
const appCreate = async (username, password, token) => {
|
const appCreate = async (username, password, token) => {
|
||||||
const acc = username.split('@');
|
const acc = username.split('@');
|
||||||
await addAccount(acc[0], acc[1], password, token);
|
await addAccount(acc[0], acc[1], password, token);
|
||||||
let access = await setLogin(acc[0], acc[1], password)
|
const access = await setLogin(acc[0], acc[1], password)
|
||||||
setWebsocket(acc[1], access.appToken)
|
store.actions.setSession({ ...access, server: acc[1] });
|
||||||
updateState({ session: true, token: access.appToken, server: acc[1] });
|
|
||||||
// store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appLogin = async (username, password) => {
|
const appLogin = async (username, password) => {
|
||||||
const acc = username.split('@');
|
const acc = username.split('@');
|
||||||
let access = await setLogin(acc[0], acc[1], password)
|
const access = await setLogin(acc[0], acc[1], password)
|
||||||
console.log(access);
|
store.actions.setSession({ ...access, server: acc[1] });
|
||||||
setWebsocket(acc[1], access.appToken)
|
|
||||||
updateState({ session: true, token: access.appToken, server: acc[1] });
|
|
||||||
// store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appLogout = () => {
|
const appLogout = () => {
|
||||||
clearWebsocket();
|
store.actions.clearSession();
|
||||||
updateState({ session: false, });
|
|
||||||
// store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setWebsocket = (server, token) => {
|
const setWebsocket = (server, token) => {
|
||||||
|
clearWebsocket();
|
||||||
ws.current = new WebSocket(`wss://${server}/status`);
|
ws.current = new WebSocket(`wss://${server}/status`);
|
||||||
ws.current.onmessage = (ev) => {
|
ws.current.onmessage = (ev) => {
|
||||||
try {
|
try {
|
||||||
let rev = JSON.parse(ev.data);
|
const rev = JSON.parse(ev.data);
|
||||||
setAppRevision(rev);
|
setAppRevision(rev);
|
||||||
updateState({ disconnected: false });
|
updateState({ disconnected: false });
|
||||||
}
|
}
|
||||||
@ -104,15 +97,26 @@ console.log(access);
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clearWebsocket = () => {
|
const clearWebsocket = () => {
|
||||||
ws.current.onclose = () => {}
|
if (ws.current) {
|
||||||
ws.current.close()
|
ws.current.onclose = () => {}
|
||||||
ws.current = null
|
ws.current.close()
|
||||||
|
ws.current = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateState({ session: false });
|
if (store.state.init) {
|
||||||
// pull store set websocket
|
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 }
|
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(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (app.state.session === true) {
|
if (app.state.session === true) {
|
||||||
navigate('/session');
|
navigate('/session');
|
||||||
}
|
}
|
||||||
if (app.state.session === false) {
|
if (app.state.session === false) {
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
}, [app]);
|
}, [app.state]);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
|
import { TouchableOpacity, Text } from 'react-native';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { AppContext } from 'context/AppContext';
|
||||||
|
|
||||||
export function Session() {
|
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