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,11 +7,13 @@ 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 { ProfileContextProvider } from 'context/ProfileContext';
export default function App() { export default function App() {
return ( return (
<StoreContextProvider> <StoreContextProvider>
<ProfileContextProvider>
<AppContextProvider> <AppContextProvider>
<NativeRouter> <NativeRouter>
<Routes> <Routes>
@ -24,6 +26,7 @@ export default function App() {
</Routes> </Routes>
</NativeRouter> </NativeRouter>
</AppContextProvider> </AppContextProvider>
</ProfileContextProvider>
</StoreContextProvider> </StoreContextProvider>
); );
} }

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; 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 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) checkResponse(profile)
return await profile.json() return await profile.json()
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setProfileImage(token, image) { export async function setProfileImage(server, token, image) {
let profile = await fetchWithTimeout(`/profile/image?agent=${token}`, { method: 'PUT', body: JSON.stringify(image) }); let profile = await fetchWithTimeout(`https://${server}/profile/image?agent=${token}`, { method: 'PUT', body: JSON.stringify(image) });
checkResponse(profile) checkResponse(profile)
return await profile.json() 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 { 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 { ProfileContext } from 'context/ProfileContext';
export function useAppContext() { export function useAppContext() {
const [state, setState] = useState({ const [state, setState] = useState({
session: null, session: null,
disconnected: null, disconnected: null,
}); });
const [appRevision, setAppRevision] = useState();
const store = useContext(StoreContext); const store = useContext(StoreContext);
const profile = useContext(ProfileContext);
const delay = useRef(2); const delay = useRef(2);
const ws = useRef(null); const ws = useRef(null);
const revision = useRef(null);
const updateState = (value) => { const updateState = (value) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }))
} }
const resetData = () => { useEffect(() => {
revision.current = null; 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 = { const actions = {
@ -32,19 +54,22 @@ export function useAppContext() {
create: async (server, username, password, token) => { create: async (server, username, password, token) => {
await addAccount(server, username, password, token); await addAccount(server, username, password, token);
const access = await setLogin(username, server, password) 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) => { access: async (server, token) => {
const access = await setAccountAccess(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) => { login: async (username, password) => {
const acc = username.split('@'); const acc = username.split('@');
const access = await setLogin(acc[0], acc[1], password) 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 () => { logout: async () => {
resetData(); await clearSession();
await store.actions.clearSession(); await store.actions.clearSession();
}, },
} }
@ -55,7 +80,7 @@ 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);
setAppRevision(rev); profile.actions.setRevision(rev.profileRevision);
updateState({ disconnected: false }); updateState({ disconnected: false });
} }
catch (err) { 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 } 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'; const DATABAG_DB = 'databag_v001.db';
export function useStoreContext() { export function useStoreContext() {
const [state, setState] = useState({ const [state, setState] = useState({});
init: false,
session: null,
sessionId: 0,
profileRevision: null,
cardRevision: null,
channelRevision: null,
accountRevision: null,
});
const db = useRef(null); const db = useRef(null);
const session = useRef(null);
const sessionId = useRef(0);
const updateState = (value) => { const updateState = (value) => {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }))
} }
useEffect(() => { const actions = {
initialize(); init: async () => {
}, []);
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.DEBUG(false);
SQLite.enablePromise(true); SQLite.enablePromise(true);
db.current = await SQLite.openDatabase({ name: DATABAG_DB, location: "default" }); 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("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 ('session', null);");
return await getAppValue(db.current, 'session');
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 = {
setSession: async (access) => { setSession: async (access) => {
await db.current.executeSql("UPDATE app SET value=? WHERE key='session';", [encodeObject(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 () => { 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]);
session.current = null;
updateState({ session: null });
}, },
setProfileRevision: (id, profileRevision) => { getProfile: async (guid) => {
if (sessionId.current === id) { const dataId = `${guid}_profile`;
updateState({ profileRevision }); return await getAppValue(db.current, dataId, {});
}
}, },
setAccountRevision: (id, accountRevision) => { setProfile: async (guid, profile) => {
if (sessionId.current === id) { const dataId = `${guid}_profile`;
updateState({ accountRevision }); await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(profile)], dataId);
}
}, },
setCardRevision: (id, cardRevision) => { getProfileRevision: async (guid) => {
if (sessionId.current === id) { const dataId = `${guid}_profileRevision`;
updateState({ cardRevision }); return await getAppValue(db.current, dataId, 0);
}
}, },
setChannelRevision: (channelRevision) => { setProfileRevision: async (guid, revision) => {
if (sessionId.current === id) { const dataId = `${guid}_profileRevision`;
updateState({ channelRevision }); await db.current.executeSql("UPDATE app SET value=? WHERE key='?';", [encodeObject(revision)], dataId);
}
}, },
} }
return { state, actions } return { state, actions }
} }