Browse Source

Merge pull request #79 from comit-network/frontend-proxy

no-contract-setup-message
Thomas Eizinger 4 years ago
committed by GitHub
parent
commit
b045f6a3ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/workflows/ci.yml
  2. 11
      README.md
  3. 2
      daemon/src/maker.rs
  4. 2
      daemon/src/routes_maker.rs
  5. 2
      daemon/src/taker.rs
  6. 2
      dprint.json
  7. 18
      frontend/dynamicApp.ts
  8. 21
      frontend/index.html
  9. 13
      frontend/maker.html
  10. 6
      frontend/maker.tsx
  11. 9
      frontend/src/Maker.tsx
  12. 15
      frontend/src/Taker.tsx
  13. 25
      frontend/src/index.tsx
  14. 21
      frontend/src/main_maker.tsx
  15. 13
      frontend/taker.html
  16. 8
      frontend/taker.tsx
  17. 2
      frontend/tsconfig.json
  18. 50
      frontend/vite.config.ts

8
.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

11
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.

2
daemon/src/maker.rs

@ -146,7 +146,7 @@ async fn main() -> Result<()> {
},
))
.mount(
"/",
"/api",
rocket::routes![
routes_maker::maker_feed,
routes_maker::post_sell_order,

2
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<watch::Receiver<Vec<Cfd>>>,
rx_order: &State<watch::Receiver<Option<Order>>>,

2
daemon/src/taker.rs

@ -156,7 +156,7 @@ async fn main() -> Result<()> {
},
))
.mount(
"/",
"/api",
rocket::routes![
routes_taker::feed,
routes_taker::post_cfd,

2
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",

18
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;
},
};
}

21
frontend/index.html

@ -1,13 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hermes Main page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/index.tsx"></script>
</body>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hermes</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./__app__.tsx"> // `__app__` is dynamically resolved by the `dynamicApp` vite plugin
</script>
</body>
</html>

13
frontend/maker.html

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hermes Maker</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main_maker.tsx"></script>
</body>
</html>

6
frontend/src/main.tsx → 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(
<React.StrictMode>

9
frontend/src/Maker.tsx

@ -17,16 +17,13 @@ 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 */
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/feed" });
const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const order = useLatestEvent<Order>(source, "order");

15
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<MarginResponse> {
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<MarginResponse>
}
export default function App() {
let source = useEventSource({ source: BASE_URL + "/feed" });
let source = useEventSource({ source: "/api/feed" });
const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const order = useLatestEvent<Order>(source, "order");
@ -152,7 +149,7 @@ export default function App() {
<Text>Quantity:</Text>
<CurrencyInputField
onChange={(valueString: string) => {
setQuantity(parse(valueString))
setQuantity(parse(valueString));
if (!order) {
return;
@ -162,8 +159,8 @@ export default function App() {
let payload: MarginRequestPayload = {
leverage: order.leverage,
price: order.price,
quantity
}
quantity,
};
calculateMargin(payload);
}}
value={format(quantity)}

25
frontend/src/index.tsx

@ -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(
<React.StrictMode>
<ChakraProvider theme={theme}>
<EventSourceProvider>
<BrowserRouter>
<Routes>
<Route path="/maker/*" element={<Maker />} />
<Route path="/taker/*" element={<Taker />} />
</Routes>
</BrowserRouter>
</EventSourceProvider>
</ChakraProvider>
</React.StrictMode>,
document.getElementById("root"),
);

21
frontend/src/main_maker.tsx

@ -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(
<React.StrictMode>
<ChakraProvider theme={theme}>
<EventSourceProvider>
<BrowserRouter>
<Maker />
</BrowserRouter>
</EventSourceProvider>
</ChakraProvider>
</React.StrictMode>,
document.getElementById("root"),
);

13
frontend/taker.html

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hermes Taker</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main_taker.tsx"></script>
</body>
</html>

8
frontend/src/main_taker.tsx → 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(
<React.StrictMode>
<ChakraProvider theme={theme}>
<EventSourceProvider>
<BrowserRouter>
<Taker />
<App />
</BrowserRouter>
</EventSourceProvider>
</ChakraProvider>

2
frontend/tsconfig.json

@ -16,5 +16,5 @@
"noEmit": true,
"jsx": "react"
},
"include": ["./src"]
"include": ["."]
}

50
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'
// }
// }
// }
// }
});

Loading…
Cancel
Save