added websocket to context

This commit is contained in:
Roland Osborne 2022-03-15 01:05:44 -07:00
parent dc2ca2bc68
commit fa8ac2cfa8
8 changed files with 289 additions and 59 deletions

View File

@ -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 (
<AppContextProvider>
<Root />
<div style={{ width: '100%', height: '100vh', backgroundColor: '#8fbea7' }}>
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
</div>
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '100%' }}>
<Root />
</div>
</AppContextProvider>
);
}

View File

@ -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 (
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '100%' }}>
<div style={{ backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', padding: '16px', borderRadius: '8px', width: '500px' }}>
<div style={{ textAlign: 'center', fontSize: '24px', fontWeight: 'bold', color: '#555555' }}>indicom</div>
<div style={{ fontSize: '12px', display: 'flex', borderBottom: '1px solid black', color: '#444444', paddingLeft: '16px', paddingRight: '16px' }}>
<span style={{ textAlign: 'center', width: '100%' }}>Communication for the Decentralized Web</span>
</div>
<Input size="large" spellCheck="false" onChange={(e) => usernameSet(e.target.value)} value={username} placeholder="username" prefix={<UserOutlined />} style={{ marginTop: '16px' }} />
<Input.Password size="large" onChange={(e) => setPassword(e.target.value)} value={password} placeholder="password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
<Button type="primary" onClick={onLogin} disabled={username===''||password===''} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign In</Button>
</div>
<Button type="link" onClick={() => setLoginMode(false)} disabled={!available} style={{ marginTop: '4px' }}>Create Account</Button>
</div>
)
}
if (context && !loginMode) {
return (
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '100%' }}>
<div style={{ backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', padding: '16px', borderRadius: '8px', width: '500px' }}>
<div style={{ textAlign: 'center', fontSize: '24px', fontWeight: 'bold', color: '#555555' }}>indicom</div>
<div style={{ fontSize: '12px', display: 'flex', borderBottom: '1px solid black', color: '#444444', paddingLeft: '16px', paddingRight: '16px' }}>
<span style={{ textAlign: 'center', width: '100%' }}>Communication for the Decentralized Web</span>
</div>
<Input size="large" spellCheck="false" addonAfter={conflict} onChange={(e) => usernameSet(e.target.value)} value={username} placeholder="username" prefix={<UserOutlined />} style={{ marginTop: '16px' }} />
<Input.Password size="large" onChange={(e) => setPassword(e.target.value)} value={password} placeholder="password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
<Input.Password size="large" onChange={(e) => setConfirmed(e.target.value)} value={confirmed} placeholder="confirm password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
<Button type="primary" onClick={onCreate} disabled={username===''||password===''||confirmed!==password||!creatable} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Create Account</Button>
</div>
<Button type="link" onClick={() => setLoginMode(true)} style={{ marginTop: '4px' }}>Account Sign In</Button>
</div>
)
}
return <></>
}

View File

@ -0,0 +1,4 @@
export function Admin() {
return <div>ADMIN</div>
}

View File

@ -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 (
<Router>
<Routes>
<Route path="/about" element={ <About /> } />
<Route path="/topic" element={ <Topic /> } />
<Route path="/" element={ <Empty /> } />
</Routes>
<Routes>
<Route path="/" element={ <Access /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/user" element={ <User /> } />
</Routes>
</Router>
)
}
function About() {
return (<div>ABOUT</div>)
}
function Topic() {
return (<div>TOPIC</div>)
}
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 (<div>EMPTY</div>)
}

View File

@ -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 <Button type="link" onClick={() => onLogout()} style={{ marginTop: '4px' }}>Sign Out User</Button>
}
return <></>
}

View File

@ -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 (
<AppContext.Provider value={{ state, actions }}>
<AppContext.Provider value={state}>
{children}
</AppContext.Provider>
);

View File

@ -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()
}

View File

@ -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
}