syncing profile

This commit is contained in:
balzack 2022-09-14 00:27:49 -07:00
parent 80fb1472e4
commit 4ca0f53da1
9 changed files with 164 additions and 116 deletions

View File

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

View File

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

View File

@ -1,4 +1,4 @@
export function getProfileImageUrl(token, revision) {
return '/profile/image?agent=' + token + "&revision=" + revision
export function getProfileImageUrl(server, token, revision) {
return `https://${server}/profile/image?agent=${token}&revision=${revision}`;
}

View File

@ -1,8 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setProfileData(token, name, location, description) {
export async function setProfileData(server, token, name, location, description) {
let data = { name: name, location: location, description: description };
let profile = await fetchWithTimeout(`/profile/data?agent=${token}`, { method: 'PUT', body: JSON.stringify(data) });
let profile = await fetchWithTimeout(`https://${server}/profile/data?agent=${token}`, { method: 'PUT', body: JSON.stringify(data) });
checkResponse(profile)
return await profile.json()
}

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setProfileImage(token, image) {
let profile = await fetchWithTimeout(`/profile/image?agent=${token}`, { method: 'PUT', body: JSON.stringify(image) });
export async function setProfileImage(server, token, image) {
let profile = await fetchWithTimeout(`https://${server}/profile/image?agent=${token}`, { method: 'PUT', body: JSON.stringify(image) });
checkResponse(profile)
return await profile.json()
}

View File

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

View File

@ -5,25 +5,47 @@ import { setAccountAccess } from 'api/setAccountAccess';
import { addAccount } from 'api/addAccount';
import { getUsername } from 'api/getUsername';
import { StoreContext } from 'context/StoreContext';
import { ProfileContext } from 'context/ProfileContext';
export function useAppContext() {
const [state, setState] = useState({
session: null,
disconnected: null,
});
const [appRevision, setAppRevision] = useState();
const store = useContext(StoreContext);
const profile = useContext(ProfileContext);
const delay = useRef(2);
const ws = useRef(null);
const revision = useRef(null);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }))
}
const resetData = () => {
revision.current = null;
useEffect(() => {
init();
}, []);
const init = async () => {
const access = await store.actions.init();
if (access) {
await setSession(access);
}
else {
updateState({ session: false });
}
}
const setSession = async (access) => {
profile.actions.setSession(access);
updateState({ session: true });
setWebsocket(access.server, access.appToken);
}
const clearSession = async () => {
profile.actions.clearSession();
updateState({ session: false });
clearWebsocket();
}
const actions = {
@ -32,19 +54,22 @@ export function useAppContext() {
create: async (server, username, password, token) => {
await addAccount(server, username, password, token);
const access = await setLogin(username, server, password)
store.actions.setSession({ ...access, server });
await setSession({ ...access, server });
await store.actions.setSession({ ...access, server});
},
access: async (server, token) => {
const access = await setAccountAccess(server, token);
store.actions.setSession({ ...access, server });
await setSession({ ...access, server });
await store.actions.setSession({ ...access, server});
},
login: async (username, password) => {
const acc = username.split('@');
const access = await setLogin(acc[0], acc[1], password)
store.actions.setSession({ ...access, server: acc[1] });
await setSession({ ...access, server: acc[1] });
await store.actions.setSession({ ...access, server: acc[1]});
},
logout: async () => {
resetData();
await clearSession();
await store.actions.clearSession();
},
}
@ -55,7 +80,7 @@ export function useAppContext() {
ws.current.onmessage = (ev) => {
try {
const rev = JSON.parse(ev.data);
setAppRevision(rev);
profile.actions.setRevision(rev.profileRevision);
updateState({ disconnected: false });
}
catch (err) {
@ -95,20 +120,6 @@ export function useAppContext() {
}
}
useEffect(() => {
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,72 @@
import { useState, useRef, useContext } from 'react';
import { getProfile } from 'api/getProfile';
import { setProfileData } from 'api/setProfileData';
import { setProfileImage } from 'api/setProfileImage';
import { getProfileImageUrl } from 'api/getProfileImageUrl';
import { StoreContext } from 'context/StoreContext';
export function useProfileContext() {
const [state, setState] = useState({
profile: {},
imageUrl: null,
});
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;
const revision = curRevision.current;
const { server, appToken, guid } = session.current;
const profile = await getProfile(server, appToken);
await store.actions.setProfile(guid, profile);
await store.actions.setProfileRevision(guid, revision);
updateState({ profile, imageUrl: getProfileImageUrl(server, appToken, revision) });
setRevision.current = revision;
syncing.current = false;
sync();
}
};
const actions = {
setSession: async (access) => {
const { guid, server, appToken } = access;
const profile = await store.actions.getProfile(guid);
const revision = await store.actions.getProfileRevision(guid);
updateState({ profile, imageUrl: getProfileImageUrl(server, appToken, revision) });
setRevision.current = revision;
curRevision.current = revision;
session.current = access;
},
clearSession: () => {
session.current = {};
updateState({ profile: null });
},
setRevision: (rev) => {
curRevision.current = rev;
sync();
},
setProfileData: async (name, location, description) => {
const { server, appToken } = session.current;
await setProfileData(server, appToken, name, location, description);
},
setProfileImage: async (image) => {
const { server, appToken } = session.current;
await setProfileImage(server, appToken, image);
},
}
return { state, actions }
}

View File

@ -4,97 +4,45 @@ import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'databag_v001.db';
export function useStoreContext() {
const [state, setState] = useState({
init: false,
session: null,
sessionId: 0,
profileRevision: null,
cardRevision: null,
channelRevision: null,
accountRevision: null,
});
const [state, setState] = useState({});
const db = useRef(null);
const session = useRef(null);
const sessionId = useRef(0);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }))
}
useEffect(() => {
initialize();
}, []);
useEffect(() => {
if (state.init && state.session && sessionId.current === state.sessionId) {
const revision = {
accountRevision: state.accountRevision,
profileRevision: state.profileRevision,
cardRevision: state.cardRevision,
channelRevision: state.channelRevision,
}
const revisionId = `${session.current.guid}_revision`;
db.current.executeSql(`UPDATE app SET value=? WHERE key='${revisionId}';`, [encodeObject(revision)]);
}
}, [state]);
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);");
session.current = await getAppValue(db.current, 'session');
if (!session.current) {
updateState({ init: true });
}
else {
const revisionId = `${session.current.guid}_revision`;
const revision = await getAppValue(db.current, revisionId, {});
updateState({ init: true, session: session.current, ...revision });
}
};
const actions = {
init: 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);");
return await getAppValue(db.current, 'session');
},
setSession: async (access) => {
await db.current.executeSql("UPDATE app SET value=? WHERE key='session';", [encodeObject(access)]);
const revisionId = `${access.guid}_revision`;
const revision = await getAppValue(db.current, revisionId, {});
session.current = access;
sessionId.current++;
updateState({ session: access, sessionId: sessionId.current, ...revision });
},
clearSession: async () => {
await db.current.executeSql("UPDATE app set value=? WHERE key='session';", [null]);
session.current = null;
updateState({ session: null });
},
setProfileRevision: (id, profileRevision) => {
if (sessionId.current === id) {
updateState({ profileRevision });
}
getProfile: async (guid) => {
const dataId = `${guid}_profile`;
return await getAppValue(db.current, dataId, {});
},
setAccountRevision: (id, accountRevision) => {
if (sessionId.current === id) {
updateState({ accountRevision });
}
setProfile: async (guid, profile) => {
const dataId = `${guid}_profile`;
await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(profile)], dataId);
},
setCardRevision: (id, cardRevision) => {
if (sessionId.current === id) {
updateState({ cardRevision });
}
getProfileRevision: async (guid) => {
const dataId = `${guid}_profileRevision`;
return await getAppValue(db.current, dataId, 0);
},
setChannelRevision: (channelRevision) => {
if (sessionId.current === id) {
updateState({ channelRevision });
}
setProfileRevision: async (guid, revision) => {
const dataId = `${guid}_profileRevision`;
await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(revision)], dataId);
},
}
return { state, actions }
}