diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx
index a202bcb..71e9385 100644
--- a/matrixgw_frontend/src/App.tsx
+++ b/matrixgw_frontend/src/App.tsx
@@ -14,6 +14,7 @@ import { HomeRoute } from "./routes/HomeRoute";
import { MatrixAuthCallback } from "./routes/MatrixAuthCallback";
import { MatrixLinkRoute } from "./routes/MatrixLinkRoute";
import { NotFoundRoute } from "./routes/NotFoundRoute";
+import { WSDebugRoute } from "./routes/WSDebugRoute";
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
@@ -43,6 +44,7 @@ export function App(): React.ReactElement {
} />
} />
} />
+ } />
} />
) : (
diff --git a/matrixgw_frontend/src/api/WsApi.ts b/matrixgw_frontend/src/api/WsApi.ts
new file mode 100644
index 0000000..8560808
--- /dev/null
+++ b/matrixgw_frontend/src/api/WsApi.ts
@@ -0,0 +1,15 @@
+import { APIClient } from "./ApiClient";
+
+export type WsMessage = {
+ type: string;
+ [k: string]: any;
+};
+
+export class WsApi {
+ /**
+ * Get WebSocket URL
+ */
+ static get WsURL(): string {
+ return APIClient.backendURL() + "/ws";
+ }
+}
diff --git a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx
index 82173bf..1c57e5b 100644
--- a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx
+++ b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx
@@ -256,7 +256,7 @@ function SyncThreadStatus(): React.ReactElement {
React.useEffect(() => {
const interval = setInterval(loadStatus, 1000);
- () => clearInterval(interval);
+ return () => clearInterval(interval);
}, []);
return (
diff --git a/matrixgw_frontend/src/routes/WSDebugRoute.tsx b/matrixgw_frontend/src/routes/WSDebugRoute.tsx
new file mode 100644
index 0000000..e7179cb
--- /dev/null
+++ b/matrixgw_frontend/src/routes/WSDebugRoute.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import { WsApi, type WsMessage } from "../api/WsApi";
+import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider";
+import { time } from "../utils/DateUtils";
+import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer";
+
+const State = {
+ Closed: "Closed",
+ Connected: "Connected",
+ Error: "Error",
+} as const;
+
+type TimestampedMessages = WsMessage & { time: number };
+
+export function WSDebugRoute(): React.ReactElement {
+ const snackbar = useSnackbar();
+
+ const [state, setState] = React.useState(State.Closed);
+ const wsRef = React.useRef(undefined);
+
+ const [messages, setMessages] = React.useState([]);
+
+ React.useEffect(() => {
+ const ws = new WebSocket(WsApi.WsURL);
+ wsRef.current = ws;
+
+ ws.onopen = () => setState(State.Connected);
+ ws.onerror = (e) => {
+ console.error(`WS Debug error! ${e}`);
+ snackbar(`WebSocket error! ${e}`);
+ setState(State.Error);
+ };
+ ws.onclose = () => {
+ setState(State.Closed);
+ wsRef.current = undefined;
+ };
+
+ ws.onmessage = (msg) => {
+ const dec = JSON.parse(msg.data);
+ setMessages((l) => {
+ return [{ time: time(), ...dec }, ...l];
+ });
+ };
+
+ return () => ws.close();
+ }, []);
+
+ return (
+
+ State: {state}
+ {JSON.stringify(messages)}
+
+ );
+}