diff --git a/net/web/src/App.js b/net/web/src/App.js
index a00974c7..659906af 100644
--- a/net/web/src/App.js
+++ b/net/web/src/App.js
@@ -1,17 +1,18 @@
-import React, { useContext, useState, useEffect, useRef } from 'react'
import login from './login.png';
-import { Input, Button } from 'antd';
-import { UserOutlined, LockOutlined } from '@ant-design/icons';
-import 'antd/dist/antd.css';
-import { BrowserRouter as Router, Routes, Route, useHistory } from "react-router-dom";
import { AppContextProvider } from './context/AppContext';
import { Root } from './components/Root';
+import 'antd/dist/antd.css';
function App() {
return (
-
+
+
![]({login})
+
+
+
+
);
}
diff --git a/net/web/src/components/Access.js b/net/web/src/components/Access.js
new file mode 100644
index 00000000..79c8b404
--- /dev/null
+++ b/net/web/src/components/Access.js
@@ -0,0 +1,106 @@
+import React, { useContext, useState, useEffect, useRef } from 'react'
+import { useNavigate } from "react-router-dom";
+import { AppContext } from '../context/AppContext';
+import { Input, Button } from 'antd';
+import { UserOutlined, LockOutlined } from '@ant-design/icons';
+
+export function Access() {
+ const [available, setAvailable] = useState(false)
+ const [username, setUsername] = useState('')
+ const [password, setPassword] = useState('')
+ const [confirmed, setConfirmed] = useState('')
+ const [creatable, setCreatable] = useState(false)
+ const [conflict, setConflict] = useState('')
+ const [loginMode, setLoginMode] = useState(true);
+ const [context, setContext] = useState(false);
+
+ const appContext = useContext(AppContext);
+ const navigate = useNavigate();
+ const debounce = useRef(null)
+
+ useEffect(() => {
+ console.log(appContext)
+ if (appContext) {
+ if (appContext.access === 'admin') {
+ navigate("/admin")
+ } else if (appContext.access === 'user') {
+ navigate("/user")
+ } else {
+ setContext(true)
+ appContext.actions.available().then(a => {
+ setAvailable(a > 0)
+ }).catch(err => {
+ console.log(err)
+ })
+ }
+ }
+ }, [appContext, navigate])
+
+ const usernameSet = (name) => {
+ setCreatable(false)
+ setUsername(name)
+ clearTimeout(debounce.current)
+ debounce.current = setTimeout(async () => {
+ let valid = await appContext.actions.username(name)
+ setCreatable(valid)
+ if (!valid) {
+ setConflict('not available')
+ } else {
+ setConflict('')
+ }
+ }, 500)
+ }
+
+ const onLogin = async () => {
+ try {
+ await appContext.actions.login(username, password)
+ } catch(err) {
+ window.alert(err)
+ }
+ }
+
+ const onCreate = async () => {
+ try {
+ await appContext.actions.create(username, password)
+ }
+ catch(err) {
+ window.alert(err)
+ }
+ }
+
+ if (context && loginMode) {
+ return (
+
+
+
indicom
+
+ Communication for the Decentralized Web
+
+
usernameSet(e.target.value)} value={username} placeholder="username" prefix={
} style={{ marginTop: '16px' }} />
+
setPassword(e.target.value)} value={password} placeholder="password" prefix={} style={{ marginTop: '16px' }} />
+
+
+
+
+ )
+ }
+ if (context && !loginMode) {
+ return (
+
+
+
indicom
+
+ Communication for the Decentralized Web
+
+
usernameSet(e.target.value)} value={username} placeholder="username" prefix={
} style={{ marginTop: '16px' }} />
+
setPassword(e.target.value)} value={password} placeholder="password" prefix={} style={{ marginTop: '16px' }} />
+ setConfirmed(e.target.value)} value={confirmed} placeholder="confirm password" prefix={} style={{ marginTop: '16px' }} />
+
+
+
+
+ )
+ }
+ return <>>
+}
+
diff --git a/net/web/src/components/Admin.js b/net/web/src/components/Admin.js
new file mode 100644
index 00000000..f92fcac2
--- /dev/null
+++ b/net/web/src/components/Admin.js
@@ -0,0 +1,4 @@
+export function Admin() {
+ return ADMIN
+}
+
diff --git a/net/web/src/components/Root.js b/net/web/src/components/Root.js
index 6fb84487..d6e95347 100644
--- a/net/web/src/components/Root.js
+++ b/net/web/src/components/Root.js
@@ -1,44 +1,19 @@
-import React, { useContext, useState, useEffect, useRef } from 'react'
-import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom";
-import { AppContext } from '../context/AppContext';
+import { HashRouter as Router, Routes, Route } from "react-router-dom";
+import { Access } from './Access';
+import { Admin } from './Admin';
+import { User } from './User';
+import 'antd/dist/antd.css';
export function Root() {
return (
-
- } />
- } />
- } />
-
+
+ } />
+ } />
+ } />
+
)
}
-function About() {
- return (ABOUT
)
-}
-
-function Topic() {
- return (TOPIC
)
-}
-
-function Empty() {
-
- const appContext = useContext(AppContext);
- const navigate = useNavigate();
-
- useEffect(() => {
- console.log(appContext)
- if (appContext.state) {
- if (appContext.state.appToken) {
- navigate("/topic")
- } else {
- navigate("/about")
- }
- }
- })
-
- return (EMPTY
)
-}
-
diff --git a/net/web/src/components/User.js b/net/web/src/components/User.js
new file mode 100644
index 00000000..6c397cfe
--- /dev/null
+++ b/net/web/src/components/User.js
@@ -0,0 +1,29 @@
+import React, { useContext, useState, useEffect } from 'react'
+import { useNavigate } from "react-router-dom";
+import { AppContext } from '../context/AppContext';
+import { Button } from 'antd';
+
+export function User() {
+ const [context, setContext] = useState(false)
+ const appContext = useContext(AppContext);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (appContext) {
+ if (appContext.access !== 'user') {
+ navigate("/")
+ }
+ setContext(true)
+ }
+ }, [appContext, navigate])
+
+ const onLogout = () => {
+ appContext.actions.logout()
+ }
+
+ if (context) {
+ return
+ }
+ return <>>
+}
+
diff --git a/net/web/src/context/AppContext.js b/net/web/src/context/AppContext.js
index 3ce23469..6f136a2d 100644
--- a/net/web/src/context/AppContext.js
+++ b/net/web/src/context/AppContext.js
@@ -4,9 +4,9 @@ import useAppContext from './useAppContext.hook';
export const AppContext = createContext({});
export function AppContextProvider({ children }) {
- const { state, actions } = useAppContext();
+ const state = useAppContext();
return (
-
+
{children}
);
diff --git a/net/web/src/context/fetchUtil.js b/net/web/src/context/fetchUtil.js
new file mode 100644
index 00000000..4058850f
--- /dev/null
+++ b/net/web/src/context/fetchUtil.js
@@ -0,0 +1,45 @@
+var base64 = require('base-64');
+
+const FETCH_TIMEOUT = 15000;
+
+function checkResponse(response) {
+ if(response.status >= 400 && response.status < 600) {
+ throw new Error(response.url + " failed");
+ }
+}
+
+async function fetchWithTimeout(url, options) {
+ return Promise.race([
+ fetch(url, options).catch(err => { throw new Error(url + ' failed'); }),
+ new Promise((_, reject) => setTimeout(() => reject(new Error(url + ' timeout')), FETCH_TIMEOUT))
+ ]);
+}
+
+export async function getAvailable() {
+ let available = await fetchWithTimeout("/account/available", { method: 'GET', timeout: FETCH_TIMEOUT })
+ checkResponse(available)
+ return await available.json()
+}
+
+export async function getUsername(name: string) {
+ let available = await fetchWithTimeout('/account/username?name=' + encodeURIComponent(name), { method: 'GET', timeout: FETCH_TIMEOUT })
+ checkResponse(available)
+ return await available.json()
+}
+
+export async function setLogin(username: string, password: string) {
+ let headers = new Headers()
+ headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password));
+ let app = { Name: "indicom", Description: "decentralized communication" }
+ let login = await fetchWithTimeout('/account/apps', { method: 'POST', timeout: FETCH_TIMEOUT, body: JSON.stringify(app), headers: headers })
+ checkResponse(login)
+ return await login.json()
+}
+
+export async function createAccount(username: string, password: string) {
+ let headers = new Headers()
+ headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
+ let profile = await fetchWithTimeout("/account/profile", { method: 'POST', timeout: FETCH_TIMEOUT, headers: headers })
+ checkResponse(profile);
+ return await profile.json()
+}
diff --git a/net/web/src/context/useAppContext.hook.js b/net/web/src/context/useAppContext.hook.js
index ff86d15d..c0560c9f 100644
--- a/net/web/src/context/useAppContext.hook.js
+++ b/net/web/src/context/useAppContext.hook.js
@@ -1,24 +1,101 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useRef } from 'react';
+import { getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
export default function useAppContext() {
const [state, setState] = useState(null);
+ const ws = useRef(null);
- const actions = {
+ const login = async (username: string, password: string) => {
+ let access = await setLogin(username, password)
+ setState({ appToken: access, access: 'user', actions: userActions });
+ localStorage.setItem("session", JSON.stringify({ token: access, access: 'user' }));
+ connectStatus(access);
+ }
+
+ const create = async (username: string, password: string) => {
+ 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(1000, "bye")
+ ws.current = null
+ setState({ actions: accessActions })
+ localStorage.removeItem("session");
+ }
+
+ const userActions = {
setListener: setListener,
clearListener: clearListener,
- login: login,
logout: logout,
}
-
+
+ const adminActions = {
+ logout: logout,
+ }
+
+ const accessActions = {
+ login: login,
+ create: create,
+ username: getUsername,
+ available: getAvailable,
+ }
+
+ const connectStatus = (token: string) => {
+ ws.current = new WebSocket("wss://" + window.location.host + "/status");
+ ws.current.onmessage = (ev) => {
+ console.log(ev)
+ }
+ ws.current.onclose = () => {
+ console.log('ws close')
+ setTimeout(() => {
+ if (ws.current != null) {
+ ws.current.onmessage = () => {}
+ ws.current.onclose = () => {}
+ ws.current.onopen = () => {}
+ ws.current.onerror = () => {}
+ connectStatus(token)
+ }
+ }, 2000)
+ }
+ ws.current.onopen = () => {
+ ws.current.send(JSON.stringify({ AppToken: token }))
+ }
+ ws.current.error = () => {
+ console.log('ws error')
+ }
+ }
+
useEffect(() => {
- const token = localStorage.getItem('app_token');
- if (token) {
- setState({ appToken: token })
+ const storage = localStorage.getItem('session');
+ if (storage != null) {
+ try {
+ const session = JSON.parse(storage)
+ if (session?.access === 'admin') {
+ setState({ appToken: session.token, access: session.access, actions: adminActions })
+ connectStatus(session.token);
+ } else if (session?.access === 'user') {
+ setState({ appToken: session.token, access: session.access, actions: userActions })
+ connectStatus(session.token);
+ } else {
+ setState({ actions: accessActions })
+ }
+ }
+ catch(err) {
+ console.log(err)
+ setState({ actions: accessActions })
+ }
} else {
- setState({ appToken: null })
+ setState({ actions: accessActions })
}
}, []);
- return { state, actions };
+
+ return state;
}
function setListener(name: string, callback: (objectId: string) => void) {
@@ -29,11 +106,4 @@ function clearListener(callback: (objectId: string) => void) {
return
}
-async function login(username: string, password: string) {
- return
-}
-
-async function logout() {
- return
-}