diff --git a/net/server/internal/api_getGroups.go b/net/server/internal/api_getGroups.go index 86845a35..1fa62415 100644 --- a/net/server/internal/api_getGroups.go +++ b/net/server/internal/api_getGroups.go @@ -38,7 +38,7 @@ func GetGroups(w http.ResponseWriter, r *http.Request) { } } - var response []*Group + response := []*Group{} for _, slot := range slots { response = append(response, getGroupModel(&slot)) } diff --git a/net/web/src/AppContext/AppContext.js b/net/web/src/AppContext/AppContext.js index 3ce23469..665eec18 100644 --- a/net/web/src/AppContext/AppContext.js +++ b/net/web/src/AppContext/AppContext.js @@ -1,5 +1,5 @@ import { createContext } from 'react'; -import useAppContext from './useAppContext.hook'; +import { useAppContext } from './useAppContext.hook'; export const AppContext = createContext({}); diff --git a/net/web/src/AppContext/fetchUtil.js b/net/web/src/AppContext/fetchUtil.js index 49875fe1..dec3e30b 100644 --- a/net/web/src/AppContext/fetchUtil.js +++ b/net/web/src/AppContext/fetchUtil.js @@ -43,3 +43,13 @@ export async function createAccount(username, password) { checkResponse(profile); return await profile.json() } + +export async function getGroups(token, revision) { + let headers = new Headers() + headers.append('Authorization', 'Bearer ' + token); + let param = revision == null ? '' : '?revision=' + revision + let groups = await fetchWithTimeout('/alias/groups' + param, { method: 'GET', timeout: FETCH_TIMEOUT, headers: headers }); + checkResponse(groups) + return await groups.json() +} + diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 1219aac4..eca1ffd2 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -1,53 +1,103 @@ import { useEffect, useState, useRef } from 'react'; -import { getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; +import { getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; -export default function useAppContext() { - const [state, setAppState] = useState(null); +async function updateGroups(token, revision, groupMap) { + let groups = await getGroups(token, revision); + for (let group of groups) { + groupMap.set(group.id, group); + } +} + +async function appCreate(username, password, updateState, setWebsocket) { + await createAccount(username, password); + let access = await setLogin(username, password) + updateState({ token: access, access: 'user' }); + setWebsocket(access) + localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' })); +} + +async function appLogin(username, password, updateState, setWebsocket) { + let access = await setLogin(username, password) + updateState({ token: access, access: 'user' }); + setWebsocket(access) + localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' })); +} + +function appLogout(updateState, clearWebsocket) { + updateState({ token: null, access: null }); + clearWebsocket() + localStorage.removeItem("session"); +} + + + +export function useAppContext() { + const [state, setState] = useState(null); + const groupRevision = useRef(null); + const groups = useRef(new Map()); const ws = useRef(null); - - const login = async (username, password) => { - let access = await setLogin(username, password) - setAppState({ appToken: access, access: 'user' }); - localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' })); - connectStatus(access); + const revision = useRef(null); + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) } - - const create = async (username, password) => { - await createAccount(username, password); - try { - await login(username, password) - } catch(err) { - throw new Error("login failed after account createion") - } - } - - const logout = () => { - ws.current.onclose = () => {} - ws.current.close() - ws.current = null - setAppState({ actions: accessActions }) - localStorage.removeItem("session"); + const updateData = (value) => { + setState((s) => { + let data = { ...s.Data, ...value } + return { ...s, Data: data } + }) } const userActions = { - logout: logout, + logout: () => { + appLogout(updateState, clearWebsocket); + } } const adminActions = { - logout: logout, + logout: () => { + appLogout(updateState, clearWebsocket); + } } const accessActions = { - login: login, - create: create, + login: async (username, password) => { + await appLogin(username, password, updateState, setWebsocket) + }, + create: async (username, password) => { + await appCreate(username, password, updateState, setWebsocket) + }, username: getUsername, available: getAvailable, } - const connectStatus = (token) => { + const processRevision = async (token) => { + while(revision.current != null) { + let rev = revision.current; + + // update group if revision changed + if (rev.group != groupRevision.current) { + await updateGroups(token, groupRevision.current, groups.current); + updateData({ groups: Array.from(groups.current.values()) }); + groupRevision.current = rev.group + } + + // check if new revision was received during processing + if (rev == revision.current) { + revision.current = null + } + } + } + + const setWebsocket = (token) => { ws.current = new WebSocket("wss://" + window.location.host + "/status"); ws.current.onmessage = (ev) => { - console.log(ev) + if (revision.current != null) { + revision.current = JSON.parse(ev.data); + } + else { + revision.current = JSON.parse(ev.data); + processRevision(token) + } } ws.current.onclose = () => { console.log('ws close') @@ -57,7 +107,7 @@ export default function useAppContext() { ws.current.onclose = () => {} ws.current.onopen = () => {} ws.current.onerror = () => {} - connectStatus(token) + setWebsocket(token); } }, 2000) } @@ -69,27 +119,33 @@ export default function useAppContext() { } } + const clearWebsocket = () => { + ws.current.onclose = () => {} + ws.current.close() + ws.current = null + } + useEffect(() => { const storage = localStorage.getItem('session'); if (storage != null) { try { const session = JSON.parse(storage) if (session?.access === 'admin') { - setAppState({ token: session.token, access: session.access }) - connectStatus(session.token); + setState({ token: session.token, access: session.access }) + setWebsocket(session.token); } else if (session?.access === 'user') { - setAppState({ token: session.token, access: session.access }) - connectStatus(session.token); + setState({ token: session.token, access: session.access }) + setWebsocket(session.token); } else { - setAppState({}) + setState({}) } } catch(err) { console.log(err) - setAppState({}) + setState({}) } } else { - setAppState({}) + setState({}) } }, []); diff --git a/net/web/src/Create/Create.jsx b/net/web/src/Create/Create.jsx index d7170705..4a0e6927 100644 --- a/net/web/src/Create/Create.jsx +++ b/net/web/src/Create/Create.jsx @@ -18,7 +18,7 @@ export function Create() { addonAfter={state.conflict} /> } onChange={(e) => actions.setPassword(e.target.value)} value={state.password} /> - } + } onChange={(e) => actions.setConfirmed(e.target.value)} value={state.confirmed} /> actions.onCreate()} disabled={actions.isDisabled()}> Create Account