From 2dec48a00441c5cc1fff80f2b4cf43fb4fd00161 Mon Sep 17 00:00:00 2001 From: Kai Vogelgesang Date: Sun, 2 Jan 2022 02:08:58 +0100 Subject: [PATCH] Implement State WebSocket --- backend/.gitignore | 2 ++ backend/Pipfile | 1 + backend/Pipfile.lock | 59 +++++++++++++++++++++++++++++++++-- backend/auth.py | 24 ++++++++++++++ backend/main.py | 33 ++++++++++++-------- backend/settings.py | 1 + backend/state.py | 33 ++++++++++++++++++++ frontend/src/pages/Index.tsx | 24 ++++++++++++-- frontend/src/tokenStorage.tsx | 5 +-- 9 files changed, 161 insertions(+), 21 deletions(-) create mode 100644 backend/state.py diff --git a/backend/.gitignore b/backend/.gitignore index f4a6e13..9dbaf0f 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,5 @@ +db.json + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/backend/Pipfile b/backend/Pipfile index a70552b..e86dc40 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -9,6 +9,7 @@ uvicorn = "==0.15" python-dotenv = "*" python-jose = {extras = ["cryptography"], version = "*"} tinydb = "*" +websockets = "*" [dev-packages] httpx = "*" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 9646439..064b456 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f0775dc9b11be56fda53dbb2a263aeccbf591a807be967b788b6edee291b4729" + "sha256": "787b11dfd8e6c14187dac5e802e341440802b59aee7df4c21767656734f56773" }, "pipfile-spec": 6, "requires": { @@ -286,12 +286,67 @@ "version": "==4.0.1" }, "uvicorn": { + "extras": [], "hashes": [ "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1", "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff" ], "index": "pypi", "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": { @@ -315,7 +370,7 @@ "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" ], - "markers": "python_full_version >= '3.5.0'", + "markers": "python_version >= '3.5'", "version": "==2.0.9" }, "h11": { diff --git a/backend/auth.py b/backend/auth.py index 061fdf7..02f0d6f 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -2,8 +2,32 @@ from jose import jwt from settings import settings + def encode(data): return jwt.encode(data, settings.secret_key) + def decode(token): 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 \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index ec9af7b..e7e97a2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,27 +1,34 @@ -from fastapi import FastAPI, Request, Response, WebSocket +from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect from settings import settings import auth - +from state import StateManager app = FastAPI() +state_manager = StateManager() -@app.get("/api/validate") +@app.get("/api/{token}/validate") 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: - data = auth.decode(token) - assert data["type"] == "frontend" - result = True - - except Exception as e: - result = False - - return {"success": result} - + while True: + await websocket.receive_json() + except WebSocketDisconnect: + await state_manager.on_disconnect(websocket) if settings.dev_mode: diff --git a/backend/settings.py b/backend/settings.py index 0e31c6d..caf9bc6 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -6,6 +6,7 @@ class Settings(BaseSettings): dev_npm_port: int = 3000 frontend_path: str = "frontend" + database_path: str = "db.json" secret_key: str diff --git a/backend/state.py b/backend/state.py new file mode 100644 index 0000000..77095f7 --- /dev/null +++ b/backend/state.py @@ -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) diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx index f0ae1e7..c9355f1 100644 --- a/frontend/src/pages/Index.tsx +++ b/frontend/src/pages/Index.tsx @@ -1,13 +1,33 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; +import { State } from "../proto"; import { TokenContext } from "../tokenStorage"; export const Index: React.FC = () => { const tokenStorage = React.useContext(TokenContext); + + const [state, setState] = useState(); + + 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 <> - Hallo i bims 1 index u d1 token bimst {tokenStorage.token} +

Hallo i bims 1 index u d1 token bimst {tokenStorage.token}

+

Der State ist:

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