syncing account and profile

This commit is contained in:
Roland Osborne 2022-09-14 12:18:16 -07:00
parent 4ca0f53da1
commit 33966512f1
9 changed files with 164 additions and 32 deletions

View File

@ -7,26 +7,29 @@ import { Session } from 'src/session/Session';
import { Admin } from 'src/admin/Admin'; import { Admin } from 'src/admin/Admin';
import { StoreContextProvider } from 'context/StoreContext'; import { StoreContextProvider } from 'context/StoreContext';
import { AppContextProvider } from 'context/AppContext'; import { AppContextProvider } from 'context/AppContext';
import { AccountContextProvider } from 'context/AccountContext';
import { ProfileContextProvider } from 'context/ProfileContext'; import { ProfileContextProvider } from 'context/ProfileContext';
export default function App() { export default function App() {
return ( return (
<StoreContextProvider> <StoreContextProvider>
<ProfileContextProvider> <AccountContextProvider>
<AppContextProvider> <ProfileContextProvider>
<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>
</ProfileContextProvider> </AppContextProvider>
</ProfileContextProvider>
</AccountContextProvider>
</StoreContextProvider> </StoreContextProvider>
); );
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function getAccountStatus(token) { export async function getAccountStatus(server, token) {
let status = await fetchWithTimeout('/account/status?agent=' + token, { method: 'GET' }); let status = await fetchWithTimeout(`https://${server}/account/status?agent=${token}`, { method: 'GET' });
checkResponse(status); checkResponse(status);
return await status.json() return await status.json()
} }

View File

@ -1,10 +1,10 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
import base64 from 'react-native-base64' import base64 from 'react-native-base64'
export async function setAccountLogin(token, username, password) { export async function setAccountLogin(server, token, username, password) {
let headers = new Headers() let headers = new Headers()
headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password)); headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
let res = await fetchWithTimeout(`/account/login?agent=${token}`, { method: 'PUT', headers }) let res = await fetchWithTimeout(`https://${server}/account/login?agent=${token}`, { method: 'PUT', headers })
checkResponse(res); checkResponse(res);
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setAccountSearchable(token, flag) { export async function setAccountSearchable(server, token, flag) {
let res = await fetchWithTimeout('/account/searchable?agent=' + token, { method: 'PUT', body: JSON.stringify(flag) }) let res = await fetchWithTimeout(`https://${server}/account/searchable?agent=${token}`, { method: 'PUT', body: JSON.stringify(flag) })
checkResponse(res); checkResponse(res);
} }

View File

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

View File

@ -0,0 +1,75 @@
import { useState, useRef, useContext } from 'react';
import { StoreContext } from 'context/StoreContext';
import { setAccountSearchable } from 'api/setAccountSearchable';
import { getAccountStatus } from 'api/getAccountStatus';
import { setAccountLogin } from 'api/setAccountLogin';
export function useAccountContext() {
const [state, setState] = useState({
status: {},
});
const store = useContext(StoreContext);
const session = useRef(null);
const curRevision = useRef(null);
const setRevision = useRef(null);
const syncing = useRef(false);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }))
}
const sync = async () => {
if (!syncing.current && setRevision.current !== curRevision.current) {
syncing.current = true;
try {
const revision = curRevision.current;
const { server, appToken, guid } = session.current;
const status = await getAccountStatus(server, appToken);
await store.actions.setAccountStatus(guid, status);
await store.actions.setAccountRevision(guid, revision);
updateState({ status });
setRevision.current = revision;
}
catch(err) {
console.log(err);
}
syncing.current = false;
sync();
}
};
const actions = {
setSession: async (access) => {
const { guid, server, appToken } = access;
const status = await store.actions.getAccountStatus(guid);
const revision = await store.actions.getAccountRevision(guid);
updateState({ status });
setRevision.current = revision;
curRevision.current = revision;
session.current = access;
},
clearSession: () => {
session.current = {};
updateState({ account: null });
},
setRevision: (rev) => {
curRevision.current = rev;
sync();
},
setSearchable: async (flag) => {
const { server, appToken } = access;
await setAccountSearchable(server, appToken, flag);
},
setLogin: async (username, password) => {
const { server, appToken } = access;
await setAccountLogin(server, appToken, username, password);
},
}
return { state, actions }
}

View File

@ -5,6 +5,7 @@ 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'; import { StoreContext } from 'context/StoreContext';
import { AccountContext } from 'context/AccountContext';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
export function useAppContext() { export function useAppContext() {
@ -13,6 +14,7 @@ export function useAppContext() {
disconnected: null, disconnected: null,
}); });
const store = useContext(StoreContext); const store = useContext(StoreContext);
const account = useContext(AccountContext);
const profile = useContext(ProfileContext); const profile = useContext(ProfileContext);
const delay = useRef(2); const delay = useRef(2);
@ -37,12 +39,14 @@ export function useAppContext() {
} }
const setSession = async (access) => { const setSession = async (access) => {
profile.actions.setSession(access); await account.actions.setSession(access);
await profile.actions.setSession(access);
updateState({ session: true }); updateState({ session: true });
setWebsocket(access.server, access.appToken); setWebsocket(access.server, access.appToken);
} }
const clearSession = async () => { const clearSession = async () => {
account.actions.clearSession();
profile.actions.clearSession(); profile.actions.clearSession();
updateState({ session: false }); updateState({ session: false });
clearWebsocket(); clearWebsocket();
@ -80,7 +84,8 @@ export function useAppContext() {
ws.current.onmessage = (ev) => { ws.current.onmessage = (ev) => {
try { try {
const rev = JSON.parse(ev.data); const rev = JSON.parse(ev.data);
profile.actions.setRevision(rev.profileRevision); profile.actions.setRevision(rev.profile);
account.actions.setRevision(rev.account);
updateState({ disconnected: false }); updateState({ disconnected: false });
} }
catch (err) { catch (err) {

View File

@ -25,13 +25,18 @@ export function useProfileContext() {
if (!syncing.current && setRevision.current !== curRevision.current) { if (!syncing.current && setRevision.current !== curRevision.current) {
syncing.current = true; syncing.current = true;
const revision = curRevision.current; try {
const { server, appToken, guid } = session.current; const revision = curRevision.current;
const profile = await getProfile(server, appToken); const { server, appToken, guid } = session.current;
await store.actions.setProfile(guid, profile); const profile = await getProfile(server, appToken);
await store.actions.setProfileRevision(guid, revision); await store.actions.setProfile(guid, profile);
updateState({ profile, imageUrl: getProfileImageUrl(server, appToken, revision) }); await store.actions.setProfileRevision(guid, revision);
setRevision.current = revision; updateState({ profile, imageUrl: getProfileImageUrl(server, appToken, revision) });
setRevision.current = revision;
}
catch(err) {
console.log(err);
}
syncing.current = false; syncing.current = false;
sync(); sync();

View File

@ -1,7 +1,7 @@
import { useEffect, useState, useRef, useContext } from 'react'; import { useEffect, useState, useRef, useContext } from 'react';
import SQLite from "react-native-sqlite-storage"; import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'databag_v001.db'; const DATABAG_DB = 'databag_v005.db';
export function useStoreContext() { export function useStoreContext() {
const [state, setState] = useState({}); const [state, setState] = useState({});
@ -26,13 +26,15 @@ export function useStoreContext() {
clearSession: async () => { clearSession: async () => {
await db.current.executeSql("UPDATE app set value=? WHERE key='session';", [null]); await db.current.executeSql("UPDATE app set value=? WHERE key='session';", [null]);
}, },
getProfile: async (guid) => { getProfile: async (guid) => {
const dataId = `${guid}_profile`; const dataId = `${guid}_profile`;
return await getAppValue(db.current, dataId, {}); return await getAppValue(db.current, dataId, {});
}, },
setProfile: async (guid, profile) => { setProfile: async (guid, profile) => {
const dataId = `${guid}_profile`; const dataId = `${guid}_profile`;
await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(profile)], dataId); await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, null);", [dataId]);
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(profile), dataId]);
}, },
getProfileRevision: async (guid) => { getProfileRevision: async (guid) => {
const dataId = `${guid}_profileRevision`; const dataId = `${guid}_profileRevision`;
@ -40,8 +42,28 @@ export function useStoreContext() {
}, },
setProfileRevision: async (guid, revision) => { setProfileRevision: async (guid, revision) => {
const dataId = `${guid}_profileRevision`; const dataId = `${guid}_profileRevision`;
await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(revision)], dataId); await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, 0);", [dataId]);
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(revision), dataId]);
}, },
getAccountStatus: async (guid) => {
const dataId = `${guid}_status`;
return await getAppValue(db.current, dataId, {});
},
setAccountStatus: async (guid, status) => {
const dataId = `${guid}_status`;
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, null);", [dataId]);
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(status), dataId]);
},
getAccountRevision: async (guid) => {
const dataId = `${guid}_accountRevision`;
return await getAppValue(db.current, dataId, 0);
},
setAccountRevision: async (guid, revision) => {
const dataId = `${guid}_accountRevision`;
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, 0);", [dataId]);
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(revision), dataId]);
},
} }
return { state, actions } return { state, actions }
} }
@ -67,6 +89,14 @@ function hasResult(res) {
return true; return true;
} }
function executeSql(sql: SQLite.SQLiteDatabase, query, params, uset) {
return new Promise((resolve, reject) => {
sql.executeSql(query, params, (tx, results) => {
resolve(results);
});
});
}
async function getAppValue(sql: SQLite.SQLiteDatabase, id: string, unset) { async function getAppValue(sql: SQLite.SQLiteDatabase, id: string, unset) {
const res = await sql.executeSql(`SELECT * FROM app WHERE key='${id}';`); const res = await sql.executeSql(`SELECT * FROM app WHERE key='${id}';`);
if (hasResult(res)) { if (hasResult(res)) {