diff --git a/README.md b/README.md index df6de64e..e285c351 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,6 @@ Implementations are available in the following languages: | C++ | [server + examples](cpp) | `foxglove-websocket` | [![](https://shields.io/conan/v/foxglove-websocket)](https://conan.io/center/foxglove-websocket) | ### Additional resources - +- https://foxglove.github.io/ws-protocol - Connect to a ws-protocol compliant server and measure data throughput - [eCAL Foxglove Bridge](https://github.com/eclipse-ecal/ecal-foxglove-bridge) – WebSocket bridge that allows users to connect eCAL systems to Foxglove Studio for easy visualization and debugging diff --git a/package.json b/package.json index c4c64131..1208ab8e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "private": true, "workspaces": { "packages": [ - "typescript/*" + "typescript/*", + "test-client-web-app" ] } } diff --git a/test-client-web-app/.eslintrc.js b/test-client-web-app/.eslintrc.js new file mode 100644 index 00000000..c17abc52 --- /dev/null +++ b/test-client-web-app/.eslintrc.js @@ -0,0 +1,22 @@ +/* eslint-env node */ +module.exports = { + env: { es2020: true }, + ignorePatterns: ["dist"], + extends: ["plugin:@foxglove/base", "plugin:@foxglove/jest"], + overrides: [ + { + files: ["*.ts", "*.tsx"], + extends: ["plugin:@foxglove/typescript"], + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: __dirname, + }, + }, + ], + rules: { + "no-warning-comments": [ + "error", + { terms: ["fixme"], location: "anywhere" }, + ], + }, +}; diff --git a/typescript/test-client-web-app/.gitignore b/test-client-web-app/.gitignore similarity index 100% rename from typescript/test-client-web-app/.gitignore rename to test-client-web-app/.gitignore diff --git a/typescript/test-client-web-app/README.md b/test-client-web-app/README.md similarity index 100% rename from typescript/test-client-web-app/README.md rename to test-client-web-app/README.md diff --git a/typescript/test-client-web-app/package.json b/test-client-web-app/package.json similarity index 100% rename from typescript/test-client-web-app/package.json rename to test-client-web-app/package.json diff --git a/typescript/test-client-web-app/public/index.html b/test-client-web-app/public/index.html similarity index 100% rename from typescript/test-client-web-app/public/index.html rename to test-client-web-app/public/index.html diff --git a/typescript/test-client-web-app/public/robots.txt b/test-client-web-app/public/robots.txt similarity index 100% rename from typescript/test-client-web-app/public/robots.txt rename to test-client-web-app/public/robots.txt diff --git a/typescript/test-client-web-app/src/App.css b/test-client-web-app/src/App.css similarity index 100% rename from typescript/test-client-web-app/src/App.css rename to test-client-web-app/src/App.css diff --git a/typescript/test-client-web-app/src/App.tsx b/test-client-web-app/src/App.tsx similarity index 83% rename from typescript/test-client-web-app/src/App.tsx rename to test-client-web-app/src/App.tsx index 32657264..0f94fed9 100644 --- a/typescript/test-client-web-app/src/App.tsx +++ b/test-client-web-app/src/App.tsx @@ -27,8 +27,22 @@ import { Tooltip, Typography, } from "@mui/material"; -import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title } from "chart.js"; -import { memo, useCallback, useEffect, useRef, useState, ReactElement } from "react"; +import { + BarElement, + CategoryScale, + Chart as ChartJS, + Legend, + LinearScale, + Title, +} from "chart.js"; +import { + memo, + useCallback, + useEffect, + useRef, + useState, + ReactElement, +} from "react"; import { Bar } from "react-chartjs-2"; import "./App.css"; @@ -83,7 +97,10 @@ function App(): ReactElement { totalNumBytes: 0, ticks: [], }); - const [config, setConfig] = useState({ runInWorker: true, subscribeNewChannels: false }); + const [config, setConfig] = useState({ + runInWorker: true, + subscribeNewChannels: false, + }); const [statusLogs, setStatusLogs] = useState([]); useEffect(() => { @@ -99,7 +116,11 @@ function App(): ReactElement { numMsgs += stats.totalNumMsgs; numBytes += stats.totalNumBytes; stats.ticks = [ - { time: now, numMsgs: stats.totalNumMsgs, numBytes: stats.totalNumBytes }, + { + time: now, + numMsgs: stats.totalNumMsgs, + numBytes: stats.totalNumBytes, + }, ...stats.ticks.slice(0, NUM_TICKS_CHANNEL_STATS - 1), ]; } @@ -140,7 +161,7 @@ function App(): ReactElement { stats.currentSubId = undefined; } }, - [client], + [client] ); const subscribeAll = useCallback( @@ -149,14 +170,18 @@ function App(): ReactElement { enableSubscription(id, action); }); }, - [channels, enableSubscription], + [channels, enableSubscription] ); useEffect(() => { for (const channel of channels) { const stats = channelStats.get(channel.id); if (!stats) { - channelStats.set(channel.id, { totalNumMsgs: 0, totalNumBytes: 0, ticks: [] }); + channelStats.set(channel.id, { + totalNumMsgs: 0, + totalNumBytes: 0, + ticks: [], + }); if (config.subscribeNewChannels) { enableSubscription(channel.id, "subscribe"); } @@ -166,9 +191,11 @@ function App(): ReactElement { const addLogEntry = useCallback( (entry: LogEntry) => { - setStatusLogs((logs: StampedLogEntry[]) => logs.concat([{ ...entry, time: new Date() }])); + setStatusLogs((logs: StampedLogEntry[]) => + logs.concat([{ ...entry, time: new Date() }]) + ); }, - [setStatusLogs], + [setStatusLogs] ); const connect = useCallback(() => { @@ -197,7 +224,11 @@ function App(): ReactElement { }) as StampedStats[], }); setStatusLogs([ - { type: "misc", value: `Connection to ${url} established`, time: new Date() }, + { + type: "misc", + value: `Connection to ${url} established`, + time: new Date(), + }, ]); }); wsClient.on("close", () => { @@ -222,7 +253,7 @@ function App(): ReactElement { return 1; } return 0; - }), + }) ); }); wsClient.on("unadvertise", (removedChannelIds: ChannelId[]) => { @@ -234,7 +265,9 @@ function App(): ReactElement { } } setChannels((currChannels: Channel[]) => - currChannels.filter((channel) => !removedChannelIds.includes(channel.id)), + currChannels.filter( + (channel) => !removedChannelIds.includes(channel.id) + ) ); }); wsClient.on("message", (event: MessageData) => { @@ -258,7 +291,9 @@ function App(): ReactElement { setClient(undefined); }, [client]); - const totalTicks = totalStats.ticks.filter((v: StampedStats | undefined) => !!v); + const totalTicks = totalStats.ticks.filter( + (v: StampedStats | undefined) => !!v + ); const firstTotalTick = totalTicks[0]; const lastTotalTick = totalTicks[totalTicks.length - 1]; const avgBandWith = @@ -272,7 +307,9 @@ function App(): ReactElement { datasets: [ { label: "Bandwidth [byte/s]", - data: totalStats.ticks.map((bt) => (bt.byteDiff ?? 0) / (UPDATE_PERIOD_MS / 1000)), + data: totalStats.ticks.map( + (bt) => (bt.byteDiff ?? 0) / (UPDATE_PERIOD_MS / 1000) + ), backgroundColor: "rgba(53, 162, 235, 0.5)", }, ], @@ -327,7 +364,10 @@ function App(): ReactElement { disabled={!!client || typeof Worker === "undefined"} checked={config.runInWorker} onChange={(_, checked) => { - setConfig((currConfig) => ({ ...currConfig, runInWorker: checked })); + setConfig((currConfig) => ({ + ...currConfig, + runInWorker: checked, + })); }} /> } @@ -349,8 +389,14 @@ function App(): ReactElement { /> - - Total msgs: {totalStats.totalNumMsgs} + + + Total msgs: {totalStats.totalNumMsgs} + Total bytes: {totalStats.totalNumBytes.toExponential(1)} @@ -359,7 +405,10 @@ function App(): ReactElement {
- +
@@ -396,9 +445,12 @@ function App(): ReactElement { const firstTick = ticks[0]; const lastTick = ticks[ticks.length - 1]; if (firstTick != undefined && lastTick != undefined) { - const timeDiffInSec = (firstTick.time - lastTick.time) / 1000; - avgFreq = (firstTick.numMsgs - lastTick.numMsgs) / timeDiffInSec; - avgBw = (firstTick.numBytes - lastTick.numBytes) / timeDiffInSec; + const timeDiffInSec = + (firstTick.time - lastTick.time) / 1000; + avgFreq = + (firstTick.numMsgs - lastTick.numMsgs) / timeDiffInSec; + avgBw = + (firstTick.numBytes - lastTick.numBytes) / timeDiffInSec; } return ( { - enableSubscription(channel.id, checked ? "subscribe" : "unsubscribe"); + enableSubscription( + channel.id, + checked ? "subscribe" : "unsubscribe" + ); }} /> @@ -423,17 +478,25 @@ function App(): ReactElement { scope="row" >{`${channel.topic} (${channel.id})`} {channel.schemaName} - {stats?.totalNumMsgs ?? 0} + + {stats?.totalNumMsgs ?? 0} + {stats?.totalNumBytes.toExponential(1) ?? 0} {stats?.totalNumBytes != undefined - ? (stats.totalNumBytes / stats.totalNumMsgs).toExponential(1) + ? ( + stats.totalNumBytes / stats.totalNumMsgs + ).toExponential(1) : 0} - {avgFreq.toFixed(1)} Hz - {avgBw.toExponential(1)} Byte/s + + {avgFreq.toFixed(1)} Hz + + + {avgBw.toExponential(1)} Byte/s + ); })} diff --git a/typescript/test-client-web-app/src/WorkerSocketAdapter.ts b/test-client-web-app/src/WorkerSocketAdapter.ts similarity index 100% rename from typescript/test-client-web-app/src/WorkerSocketAdapter.ts rename to test-client-web-app/src/WorkerSocketAdapter.ts diff --git a/typescript/test-client-web-app/src/index.css b/test-client-web-app/src/index.css similarity index 100% rename from typescript/test-client-web-app/src/index.css rename to test-client-web-app/src/index.css diff --git a/typescript/test-client-web-app/src/index.tsx b/test-client-web-app/src/index.tsx similarity index 94% rename from typescript/test-client-web-app/src/index.tsx rename to test-client-web-app/src/index.tsx index 7e2b31cc..c5cc5364 100644 --- a/typescript/test-client-web-app/src/index.tsx +++ b/test-client-web-app/src/index.tsx @@ -13,5 +13,5 @@ const root = ReactDOM.createRoot(document.getElementById("root")!); root.render( - , + ); diff --git a/typescript/test-client-web-app/src/react-app-env.d.ts b/test-client-web-app/src/react-app-env.d.ts similarity index 100% rename from typescript/test-client-web-app/src/react-app-env.d.ts rename to test-client-web-app/src/react-app-env.d.ts diff --git a/typescript/test-client-web-app/src/worker.js b/test-client-web-app/src/worker.js similarity index 92% rename from typescript/test-client-web-app/src/worker.js rename to test-client-web-app/src/worker.js index 939b8031..b4ebeaf0 100644 --- a/typescript/test-client-web-app/src/worker.js +++ b/test-client-web-app/src/worker.js @@ -29,7 +29,10 @@ self.onmessage = (event) => { }); }; ws.onclose = (wsEvent) => { - send({ type: "close", data: JSON.parse(JSON.stringify(wsEvent) ?? "{}") }); + send({ + type: "close", + data: JSON.parse(JSON.stringify(wsEvent) ?? "{}"), + }); }; ws.onmessage = (wsEvent) => { if (wsEvent.data instanceof ArrayBuffer) { @@ -38,7 +41,7 @@ self.onmessage = (event) => { type: "message", data: wsEvent.data, }, - [wsEvent.data], + [wsEvent.data] ); } else { send({ diff --git a/typescript/test-client-web-app/tsconfig.json b/test-client-web-app/tsconfig.json similarity index 77% rename from typescript/test-client-web-app/tsconfig.json rename to test-client-web-app/tsconfig.json index 52f4644d..0711d369 100644 --- a/typescript/test-client-web-app/tsconfig.json +++ b/test-client-web-app/tsconfig.json @@ -7,7 +7,7 @@ "lib": ["es2020", "dom"], "jsx": "react-jsx", "paths": { - "@foxglove/ws-protocol": ["../ws-protocol/src"] + "@foxglove/ws-protocol": ["../typescript/ws-protocol/src"] } } } diff --git a/typescript/test-client-web-app/webpack.config.js b/test-client-web-app/webpack.config.js similarity index 100% rename from typescript/test-client-web-app/webpack.config.js rename to test-client-web-app/webpack.config.js