Implement State WebSocket

This commit is contained in:
Kai Vogelgesang 2022-01-02 02:08:58 +01:00
parent 445587e596
commit 2dec48a004
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
9 changed files with 161 additions and 21 deletions

2
backend/.gitignore vendored
View File

@ -1,3 +1,5 @@
db.json
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View File

@ -9,6 +9,7 @@ uvicorn = "==0.15"
python-dotenv = "*" python-dotenv = "*"
python-jose = {extras = ["cryptography"], version = "*"} python-jose = {extras = ["cryptography"], version = "*"}
tinydb = "*" tinydb = "*"
websockets = "*"
[dev-packages] [dev-packages]
httpx = "*" httpx = "*"

59
backend/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f0775dc9b11be56fda53dbb2a263aeccbf591a807be967b788b6edee291b4729" "sha256": "787b11dfd8e6c14187dac5e802e341440802b59aee7df4c21767656734f56773"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -286,12 +286,67 @@
"version": "==4.0.1" "version": "==4.0.1"
}, },
"uvicorn": { "uvicorn": {
"extras": [],
"hashes": [ "hashes": [
"sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1", "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1",
"sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff" "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.15" "version": "==0.15"
},
"websockets": {
"hashes": [
"sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657",
"sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c",
"sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485",
"sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c",
"sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5",
"sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9",
"sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d",
"sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4",
"sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f",
"sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b",
"sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053",
"sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc",
"sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519",
"sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127",
"sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a",
"sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37",
"sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909",
"sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530",
"sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155",
"sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f",
"sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be",
"sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534",
"sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72",
"sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd",
"sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db",
"sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f",
"sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e",
"sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c",
"sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7",
"sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e",
"sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a",
"sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb",
"sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0",
"sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123",
"sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6",
"sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8",
"sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17",
"sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9",
"sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536",
"sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7",
"sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69",
"sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c",
"sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54",
"sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89",
"sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60",
"sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d",
"sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512",
"sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58"
],
"index": "pypi",
"version": "==10.1"
} }
}, },
"develop": { "develop": {
@ -315,7 +370,7 @@
"sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
"sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
], ],
"markers": "python_full_version >= '3.5.0'", "markers": "python_version >= '3.5'",
"version": "==2.0.9" "version": "==2.0.9"
}, },
"h11": { "h11": {

View File

@ -2,8 +2,32 @@ from jose import jwt
from settings import settings from settings import settings
def encode(data): def encode(data):
return jwt.encode(data, settings.secret_key) return jwt.encode(data, settings.secret_key)
def decode(token): def decode(token):
return jwt.decode(token, settings.secret_key) return jwt.decode(token, settings.secret_key)
def validate_frontend(token):
try:
data = decode(token)
assert data["type"] == "frontend"
return True
except Exception as e:
print(e)
return False
def validate_computer(token):
try:
data = decode(token)
assert data["type"] == "computer"
return True
except Exception as e:
print(e)
return False

View File

@ -1,27 +1,34 @@
from fastapi import FastAPI, Request, Response, WebSocket from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
from settings import settings from settings import settings
import auth import auth
from state import StateManager
app = FastAPI() app = FastAPI()
state_manager = StateManager()
@app.get("/api/validate") @app.get("/api/{token}/validate")
async def validate_token(token: str): async def validate_token(token: str):
return {"success": auth.validate_frontend(token)}
result = None
@app.websocket("/api/{token}/state")
async def state_updates_websocket(websocket: WebSocket, token: str):
if not auth.validate_frontend(token):
await websocket.close()
return
await websocket.accept()
await state_manager.on_connect(websocket)
try: try:
data = auth.decode(token) while True:
assert data["type"] == "frontend" await websocket.receive_json()
result = True except WebSocketDisconnect:
await state_manager.on_disconnect(websocket)
except Exception as e:
result = False
return {"success": result}
if settings.dev_mode: if settings.dev_mode:

View File

@ -6,6 +6,7 @@ class Settings(BaseSettings):
dev_npm_port: int = 3000 dev_npm_port: int = 3000
frontend_path: str = "frontend" frontend_path: str = "frontend"
database_path: str = "db.json"
secret_key: str secret_key: str

33
backend/state.py Normal file
View File

@ -0,0 +1,33 @@
import asyncio
from tinydb import TinyDB
from settings import settings
db = TinyDB(settings.database_path)
computers = db.table("computers")
class StateManager:
def __init__(self):
self.websockets = set()
self.current_state = None
self.update_state()
def update_state(self):
self.current_state = {"computers": computers.all()}
async def push_state(self, socket):
await socket.send_json(self.current_state)
async def on_connect(self, socket):
self.websockets.add(socket)
await self.push_state(socket)
async def on_disconnect(self, socket):
self.websockets.remove(socket)
async def on_change(self):
self.update_state()
await asyncio.gather(self.push_state(socket) for socket in self.websockets)

View File

@ -1,13 +1,33 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { State } from "../proto";
import { TokenContext } from "../tokenStorage"; import { TokenContext } from "../tokenStorage";
export const Index: React.FC = () => { export const Index: React.FC = () => {
const tokenStorage = React.useContext(TokenContext); const tokenStorage = React.useContext(TokenContext);
const [state, setState] = useState<State>();
useEffect(() => {
const url = new URL(`api/${tokenStorage.token}/state`, window.location.href);
url.protocol = url.protocol.replace("http", "ws");
const socket = new WebSocket(url.href);
socket.onmessage = (e) => {
let newState = JSON.parse(e.data) as State;
setState(newState);
}
return () => { socket.close(); };
}, [tokenStorage.token]);
return <> return <>
Hallo i bims 1 index u d1 token bimst {tokenStorage.token} <p>Hallo i bims 1 index u d1 token bimst {tokenStorage.token}</p>
<button onClick={tokenStorage.reset}>Resetteroni</button> <button onClick={tokenStorage.reset}>Resetteroni</button>
<p>Der State ist:</p>
{JSON.stringify(state)}
</> </>
} }

View File

@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from "react";
import Login from "./Login"; import Login from "./Login";
const LOCAL_STORAGE_KEY = "token"; const LOCAL_STORAGE_KEY = "token";
const VALIDATE_ENDPOINT = "api/validate";
export type Token = string | null; export type Token = string | null;
export type TokenStorage = { export type TokenStorage = {
@ -37,9 +36,7 @@ export const TokenProvider: React.FC = (props) => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (tokenStorage.token) { if (tokenStorage.token) {
const response = await fetch(`${VALIDATE_ENDPOINT}?` + new URLSearchParams({ const response = await fetch(`api/${token}/validate`);
token: tokenStorage.token,
}));
const data = await response.json() as { "success": boolean }; const data = await response.json() as { "success": boolean };