From 58297e2c2be13c81845558aa0bcddf942563d5b9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 16 Sep 2021 18:49:47 +1000 Subject: [PATCH 1/4] Have dprint format TypeScript and JavaScript files --- dprint.json | 2 +- frontend/src/Maker.tsx | 2 +- frontend/src/Taker.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dprint.json b/dprint.json index e383bad..3241e34 100644 --- a/dprint.json +++ b/dprint.json @@ -7,7 +7,7 @@ "wrap_comments": true, "comment_width": 120 }, - "includes": ["**/*.{md,rs,toml}"], + "includes": ["**/*.{md,rs,toml,ts,tsx,js}"], "excludes": ["**/target", "**/sqlx-data.json", "frontend/dist", diff --git a/frontend/src/Maker.tsx b/frontend/src/Maker.tsx index a5f966e..94e97d1 100644 --- a/frontend/src/Maker.tsx +++ b/frontend/src/Maker.tsx @@ -17,11 +17,11 @@ import { useAsync } from "react-async"; import { Route, Routes } from "react-router-dom"; import { useEventSource } from "react-sse-hooks"; import "./App.css"; -import OrderTile from "./components/OrderTile"; import CfdTile from "./components/CfdTile"; import CurrencyInputField from "./components/CurrencyInputField"; import useLatestEvent from "./components/Hooks"; import NavLink from "./components/NavLink"; +import OrderTile from "./components/OrderTile"; import { Cfd, Order } from "./components/Types"; /* TODO: Change from localhost:8001 */ diff --git a/frontend/src/Taker.tsx b/frontend/src/Taker.tsx index 089d31e..bfa859b 100644 --- a/frontend/src/Taker.tsx +++ b/frontend/src/Taker.tsx @@ -152,7 +152,7 @@ export default function App() { Quantity: { - setQuantity(parse(valueString)) + setQuantity(parse(valueString)); if (!order) { return; @@ -162,8 +162,8 @@ export default function App() { let payload: MarginRequestPayload = { leverage: order.leverage, price: order.price, - quantity - } + quantity, + }; calculateMargin(payload); }} value={format(quantity)} From 58208579fda473afb86531b9b07f7eca5442a748 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 17 Sep 2021 10:30:59 +1000 Subject: [PATCH 2/4] Declare entire frontend/ dir as typescript project This ensures type-checks on `vite.config.ts` and other files. --- frontend/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 6414703..22a4d8d 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -16,5 +16,5 @@ "noEmit": true, "jsx": "react" }, - "include": ["./src"] + "include": ["."] } From aa2b4fb0cef65f88ad5563cfa665739b46e71698 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 16 Sep 2021 18:51:39 +1000 Subject: [PATCH 3/4] Remove hardcoded absolute URLs from the frontend source code Achieving this is rather tricky due to the nature of our multi-page project. We need to solve several problems: 1. We want a single npm project that produces two independent bundles. 2. We want our paths to be relative to the serving URL, that is `localhost:3000` in development and in production, whereever the backend is hosted. 3. We have independent backends, hence requiring different `server.proxy` configurations. We solve (1) by using vite (already prior to this commit). To solve (2), we simply remove all absolute URLs from the code and replace them with absolute paths which will be relative to the serving host. This creates a problem: Prior to this patch, we only have one devServer running that would serve both frontends under a different sub-directory (/maker and /taker). Even though this worked, it was impossible to create a proxy configuration that would: - Forward API requests from `/maker` to `localhost:8001` - Forward API requests from `/taker` to `localhost:8000` Because in both cases, the API requests would simply start with `/api`, making them indistinguishable from each other. To solve this problem, we needed to serve each frontend separately. Doing so would allow us to have dedicated proxy server configurations and forward the requests to `/api` to the correct backend. Unfortunately, the intuitive approach of solving this (have a `maker.html` and `taker.html` file) does not work. With React being a client-side routing framework, full page-reloads would be broken with this approach because they would be looking for an `index.html` file which doesn't exist. To work around this issue, our final solution is: 1. Use a dynamic ID to reference the desired app from within the `index.html`: `__app__` 2. Use a vite plugin to resolve this ID to the file in question: `maker.tsx` or `taker.tsx` Fixes #6. --- .github/workflows/ci.yml | 8 +++- README.md | 11 +++-- daemon/src/maker.rs | 2 +- daemon/src/taker.rs | 2 +- frontend/dynamicApp.ts | 18 ++++++++ frontend/index.html | 21 ++++----- frontend/maker.html | 13 ------ frontend/{src/main.tsx => maker.tsx} | 6 +-- frontend/src/Maker.tsx | 7 +-- frontend/src/Taker.tsx | 9 ++-- frontend/src/index.tsx | 25 ----------- frontend/src/main_maker.tsx | 21 --------- frontend/taker.html | 13 ------ frontend/{src/main_taker.tsx => taker.tsx} | 8 ++-- frontend/vite.config.ts | 50 ++++++++-------------- 15 files changed, 73 insertions(+), 141 deletions(-) create mode 100644 frontend/dynamicApp.ts delete mode 100644 frontend/maker.html rename frontend/{src/main.tsx => maker.tsx} (85%) delete mode 100644 frontend/src/index.tsx delete mode 100644 frontend/src/main_maker.tsx delete mode 100644 frontend/taker.html rename frontend/{src/main_taker.tsx => taker.tsx} (81%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 165e4a7..569885c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,8 @@ jobs: cache-dependency-path: frontend/yarn.lock - run: yarn install - run: yarn run eslint - - run: yarn build + - run: APP=maker yarn build + - run: APP=taker yarn build test_daemons: strategy: @@ -68,6 +69,9 @@ jobs: run: | target/debug/maker --data-dir=/tmp/maker --generate-seed & sleep 5s # Wait for maker to start + target/debug/taker --data-dir=/tmp/taker --generate-seed & sleep 5s # Wait for taker to start - curl --fail http://localhost:8000/alive + + curl --fail http://localhost:8000/api/alive + curl --fail http://localhost:8001/api/alive diff --git a/README.md b/README.md index 5f1ceeb..65b4b0d 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,15 @@ Note: The sqlite databases for maker and taker are currently created in the proj ## Starting the maker and taker frontend We use a single react project for hosting both the taker and the maker frontends. - -To start it in development mode: +However, the development environment still needs to be start twice! +Which frontend to start is configured via the `APP` environment variable. ```bash -cd frontend && yarn dev +cd frontend; +APP=taker yarn dev +APP=maker yarn dev ``` -- To access maker: [Maker](http://localhost:3000/maker) -- To access taker: [Taker](http://localhost:3000/taker) - Bundling the web frontend and serving it from the respective daemon is yet to be configured. At the moment you will need a browser extension to allow CORS headers like `CORS Everywhere` ([Firefox Extension](https://addons.mozilla.org/en-US/firefox/addon/cors-everywhere/)) to use the frontends. diff --git a/daemon/src/maker.rs b/daemon/src/maker.rs index eb7b3c6..b87ce46 100644 --- a/daemon/src/maker.rs +++ b/daemon/src/maker.rs @@ -145,7 +145,7 @@ async fn main() -> Result<()> { }, )) .mount( - "/", + "/api", rocket::routes![ routes_maker::maker_feed, routes_maker::post_sell_order, diff --git a/daemon/src/taker.rs b/daemon/src/taker.rs index 43295c2..3ae790b 100644 --- a/daemon/src/taker.rs +++ b/daemon/src/taker.rs @@ -155,7 +155,7 @@ async fn main() -> Result<()> { }, )) .mount( - "/", + "/api", rocket::routes![ routes_taker::feed, routes_taker::post_cfd, diff --git a/frontend/dynamicApp.ts b/frontend/dynamicApp.ts new file mode 100644 index 0000000..8161555 --- /dev/null +++ b/frontend/dynamicApp.ts @@ -0,0 +1,18 @@ +import { Plugin } from "vite"; + +export default function dynamicApp(app: string): Plugin { + return { + name: "dynamicApp", // required, will show up in warnings and errors + resolveId: (id) => { + // For some reason these are different? + const productionBuildId = "./__app__.tsx"; + const devBuildId = "/__app__.tsx"; + + if (id === productionBuildId || id === devBuildId) { + return `${__dirname}/${app}.tsx`; + } + + return null; + }, + }; +} diff --git a/frontend/index.html b/frontend/index.html index f71b270..8546184 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,14 @@ - - - - - Hermes Main page - - -
- - + + + + + Hermes + + +
+ + diff --git a/frontend/maker.html b/frontend/maker.html deleted file mode 100644 index 6d9dc93..0000000 --- a/frontend/maker.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Hermes Maker - - -
- - - diff --git a/frontend/src/main.tsx b/frontend/maker.tsx similarity index 85% rename from frontend/src/main.tsx rename to frontend/maker.tsx index a0dc897..5c000ef 100644 --- a/frontend/src/main.tsx +++ b/frontend/maker.tsx @@ -3,9 +3,9 @@ import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; import { EventSourceProvider } from "react-sse-hooks"; -import "./index.css"; -import App from "./Maker"; -import theme from "./theme"; +import "./src/index.css"; +import App from "./src/Maker"; +import theme from "./src/theme"; ReactDOM.render( diff --git a/frontend/src/Maker.tsx b/frontend/src/Maker.tsx index 94e97d1..e629a80 100644 --- a/frontend/src/Maker.tsx +++ b/frontend/src/Maker.tsx @@ -24,9 +24,6 @@ import NavLink from "./components/NavLink"; import OrderTile from "./components/OrderTile"; import { Cfd, Order } from "./components/Types"; -/* TODO: Change from localhost:8001 */ -const BASE_URL = "http://localhost:8001"; - interface CfdSellOrderPayload { price: number; min_quantity: number; @@ -34,7 +31,7 @@ interface CfdSellOrderPayload { } async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) { - let res = await axios.post(BASE_URL + `/order/sell`, JSON.stringify(payload)); + let res = await axios.post(`/api/order/sell`, JSON.stringify(payload)); if (!res.status.toString().startsWith("2")) { console.log("Status: " + res.status + ", " + res.statusText); @@ -43,7 +40,7 @@ async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) { } export default function App() { - let source = useEventSource({ source: BASE_URL + "/maker-feed" }); + let source = useEventSource({ source: "/api/maker-feed" }); const cfds = useLatestEvent(source, "cfds"); const order = useLatestEvent(source, "order"); diff --git a/frontend/src/Taker.tsx b/frontend/src/Taker.tsx index bfa859b..acbea83 100644 --- a/frontend/src/Taker.tsx +++ b/frontend/src/Taker.tsx @@ -11,9 +11,6 @@ import useLatestEvent from "./components/Hooks"; import NavLink from "./components/NavLink"; import { Cfd, Order } from "./components/Types"; -/* TODO: Change from localhost:8000 */ -const BASE_URL = "http://localhost:8000"; - interface CfdTakeRequestPayload { order_id: string; quantity: number; @@ -30,7 +27,7 @@ interface MarginResponse { } async function postCfdTakeRequest(payload: CfdTakeRequestPayload) { - let res = await axios.post(BASE_URL + `/cfd`, JSON.stringify(payload)); + let res = await axios.post(`/api/cfd`, JSON.stringify(payload)); if (!res.status.toString().startsWith("2")) { throw new Error("failed to create new CFD take request: " + res.status + ", " + res.statusText); @@ -38,7 +35,7 @@ async function postCfdTakeRequest(payload: CfdTakeRequestPayload) { } async function getMargin(payload: MarginRequestPayload): Promise { - let res = await axios.post(BASE_URL + `/calculate/margin`, JSON.stringify(payload)); + let res = await axios.post(`/api/calculate/margin`, JSON.stringify(payload)); if (!res.status.toString().startsWith("2")) { throw new Error("failed to create new CFD take request: " + res.status + ", " + res.statusText); @@ -48,7 +45,7 @@ async function getMargin(payload: MarginRequestPayload): Promise } export default function App() { - let source = useEventSource({ source: BASE_URL + "/feed" }); + let source = useEventSource({ source: "/api/feed" }); const cfds = useLatestEvent(source, "cfds"); const order = useLatestEvent(source, "order"); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx deleted file mode 100644 index dc2746c..0000000 --- a/frontend/src/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ChakraProvider } from "@chakra-ui/react"; -import React from "react"; -import ReactDOM from "react-dom"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; -import { EventSourceProvider } from "react-sse-hooks"; -import "./index.css"; -import Maker from "./Maker"; -import Taker from "./Taker"; -import theme from "./theme"; - -ReactDOM.render( - - - - - - } /> - } /> - - - - - , - document.getElementById("root"), -); diff --git a/frontend/src/main_maker.tsx b/frontend/src/main_maker.tsx deleted file mode 100644 index a90ce15..0000000 --- a/frontend/src/main_maker.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ChakraProvider } from "@chakra-ui/react"; -import React from "react"; -import ReactDOM from "react-dom"; -import { BrowserRouter } from "react-router-dom"; -import { EventSourceProvider } from "react-sse-hooks"; -import "./index.css"; -import Maker from "./Maker"; -import theme from "./theme"; - -ReactDOM.render( - - - - - - - - - , - document.getElementById("root"), -); diff --git a/frontend/taker.html b/frontend/taker.html deleted file mode 100644 index f15130b..0000000 --- a/frontend/taker.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Hermes Taker - - -
- - - diff --git a/frontend/src/main_taker.tsx b/frontend/taker.tsx similarity index 81% rename from frontend/src/main_taker.tsx rename to frontend/taker.tsx index daba5be..7076c3a 100644 --- a/frontend/src/main_taker.tsx +++ b/frontend/taker.tsx @@ -3,16 +3,16 @@ import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; import { EventSourceProvider } from "react-sse-hooks"; -import "./index.css"; -import Taker from "./Taker"; -import theme from "./theme"; +import "./src/index.css"; +import App from "./src/Taker"; +import theme from "./src/theme"; ReactDOM.render( - + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index b531056..3003308 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,18 @@ -import { resolve } from "path"; - import reactRefresh from "@vitejs/plugin-react-refresh"; +import { resolve } from "path"; import { defineConfig } from "vite"; +import dynamicApp from "./dynamicApp"; + +const app = process.env.APP; + +if (!app || (app !== "maker" && app !== "taker")) { + throw new Error("APP environment variable needs to be set to `maker` `taker`"); +} + +const backendPorts = { + "taker": 8000, + "maker": 8001, +}; // https://vitejs.dev/config/ export default defineConfig({ @@ -11,40 +22,17 @@ export default defineConfig({ ? [reactRefresh()] : [] ), + dynamicApp(app), ], build: { rollupOptions: { - input: { - maker: resolve(__dirname, "maker.html"), - taker: resolve(__dirname, "taker.html"), - }, + input: resolve(__dirname, `index.html`), }, + outDir: `dist/${app}`, }, server: { - open: "/maker", + proxy: { + "/api": `http://localhost:${backendPorts[app]}`, + }, }, - // server: { - // proxy: { - // '/foo': 'http://localhost:4567', - // '/api': { - // target: 'http://jsonplaceholder.typicode.com', - // changeOrigin: true, - // rewrite: (path) => path.replace(/^\/api/, '') - // }, - // // with RegEx - // '^/fallback/.*': { - // target: 'http://jsonplaceholder.typicode.com', - // changeOrigin: true, - // rewrite: (path) => path.replace(/^\/fallback/, '') - // }, - // // Using the proxy instance - // '/api': { - // target: 'http://jsonplaceholder.typicode.com', - // changeOrigin: true, - // configure: (proxy, options) => { - // // proxy will be an instance of 'http-proxy' - // } - // } - // } - // } }); From 7ab1ee31ec42aebb2ae839f20b246b07b3e99041 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 16 Sep 2021 19:05:44 +1000 Subject: [PATCH 4/4] Rename the maker's feed endpoint to just /feed --- daemon/src/routes_maker.rs | 2 +- frontend/src/Maker.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/src/routes_maker.rs b/daemon/src/routes_maker.rs index ca438db..765d0f3 100644 --- a/daemon/src/routes_maker.rs +++ b/daemon/src/routes_maker.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use tokio::select; use tokio::sync::{mpsc, watch}; -#[rocket::get("/maker-feed")] +#[rocket::get("/feed")] pub async fn maker_feed( rx_cfds: &State>>, rx_order: &State>>, diff --git a/frontend/src/Maker.tsx b/frontend/src/Maker.tsx index e629a80..18264c5 100644 --- a/frontend/src/Maker.tsx +++ b/frontend/src/Maker.tsx @@ -40,7 +40,7 @@ async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) { } export default function App() { - let source = useEventSource({ source: "/api/maker-feed" }); + let source = useEventSource({ source: "/api/feed" }); const cfds = useLatestEvent(source, "cfds"); const order = useLatestEvent(source, "order");