Implement State WebSocket
This commit is contained in:
parent
445587e596
commit
2dec48a004
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
db.json
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
@ -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
59
backend/Pipfile.lock
generated
@ -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": {
|
||||||
|
@ -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
|
@ -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:
|
||||||
|
|
||||||
|
@ -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
33
backend/state.py
Normal 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)
|
@ -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)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 };
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user