From c770d231d1c2e65dec1f4387516e3f57976be7fd Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 16 Sep 2022 16:16:33 +0200 Subject: [PATCH] Add frontend API client (#242) * Frontend API client * Test * CR updates --- frontend/src/components/BookPage.js | 4 +- frontend/src/components/BottomBar.js | 25 +-- .../components/Charts/DepthChart/index.tsx | 4 +- frontend/src/components/MakerPage.js | 43 +++-- frontend/src/components/OrderPage.js | 59 ++----- frontend/src/components/TradeBox.js | 160 ++++++------------ frontend/src/components/UserGenPage.js | 34 ++-- .../src/services/api/ApiWebClient/index.ts | 40 +++++ frontend/src/services/api/index.ts | 10 ++ 9 files changed, 157 insertions(+), 222 deletions(-) create mode 100644 frontend/src/services/api/ApiWebClient/index.ts create mode 100644 frontend/src/services/api/index.ts diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index 83e614c9..b06ca184 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -31,6 +31,7 @@ import { pn, amountToString } from '../utils/prettyNumbers'; import PaymentText from './PaymentText'; import DepthChart from './Charts/DepthChart'; import RobotAvatar from './Robots/RobotAvatar'; +import { apiClient } from '../services/api/index'; // Icons import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material'; @@ -51,8 +52,7 @@ class BookPage extends Component { getOrderDetails(type, currency) { this.props.setAppState({ bookLoading: true }); - fetch('/api/book' + '?currency=' + currency + '&type=' + type) - .then((response) => response.json()) + apiClient.get('/api/book?currency=' + currency + '&type=' + type) .then((data) => this.props.setAppState({ bookNotFound: data.not_found, diff --git a/frontend/src/components/BottomBar.js b/frontend/src/components/BottomBar.js index 5942f783..b3948a70 100644 --- a/frontend/src/components/BottomBar.js +++ b/frontend/src/components/BottomBar.js @@ -18,6 +18,7 @@ import { import MediaQuery from 'react-responsive'; import Flags from 'country-flag-icons/react/3x2'; import { Link as LinkRouter } from 'react-router-dom'; +import { apiClient } from '../services/api'; // Icons import BarChartIcon from '@mui/icons-material/BarChart'; @@ -71,8 +72,7 @@ class BottomBar extends Component { getInfo() { this.setState(null); - fetch('/api/info/') - .then((response) => response.json()) + apiClient.get('/api/info/') .then( (data) => this.setState(data) & @@ -122,16 +122,9 @@ class BottomBar extends Component { showRewardsSpinner: true, }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - invoice: rewardInvoice, - }), - }; - fetch('/api/reward/', requestOptions) - .then((response) => response.json()) - .then( + apiClient.post('/api/reward/', { + invoice: rewardInvoice, + }).then( (data) => this.setState({ badInvoice: data.bad_invoice, @@ -147,13 +140,7 @@ class BottomBar extends Component { }; handleSetStealthInvoice = (wantsStealth) => { - const requestOptions = { - method: 'PUT', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ wantsStealth }), - }; - fetch('/api/stealth/', requestOptions) - .then((response) => response.json()) + apiClient.put('/api/stealth/', { wantsStealth }) .then((data) => this.props.setAppState({ stealthInvoices: data.wantsStealth })); }; diff --git a/frontend/src/components/Charts/DepthChart/index.tsx b/frontend/src/components/Charts/DepthChart/index.tsx index 3f685eb8..c50973dc 100644 --- a/frontend/src/components/Charts/DepthChart/index.tsx +++ b/frontend/src/components/Charts/DepthChart/index.tsx @@ -29,6 +29,7 @@ import currencyDict from '../../../../static/assets/currencies.json'; import PaymentText from '../../PaymentText'; import getNivoScheme from '../NivoScheme'; import median from '../../../utils/match'; +import { apiClient } from '../../../services/api/index'; interface DepthChartProps { bookLoading: boolean; @@ -62,8 +63,7 @@ const DepthChart: React.FC = ({ useEffect(() => { if (Object.keys(limits).length === 0) { - fetch('/api/limits/') - .then(async (response) => await response.json()) + apiClient.get('/api/limits/') .then((data) => { setAppState({ limits: data }); }); diff --git a/frontend/src/components/MakerPage.js b/frontend/src/components/MakerPage.js index f7c23415..641321df 100644 --- a/frontend/src/components/MakerPage.js +++ b/frontend/src/components/MakerPage.js @@ -37,6 +37,7 @@ import { LocalizationProvider, TimePicker } from '@mui/x-date-pickers'; import DateFnsUtils from '@date-io/date-fns'; import { Link as LinkRouter } from 'react-router-dom'; import { StoreTokenDialog, NoRobotDialog } from './Dialogs'; +import { apiClient } from '../services/api'; import FlagWithProps from './FlagWithProps'; import AutocompletePayments from './AutocompletePayments'; @@ -104,8 +105,7 @@ class MakerPage extends Component { getLimits() { this.setState({ loadingLimits: true }); - fetch('/api/limits/') - .then((response) => response.json()) + apiClient.get('/api/limits/') .then((data) => this.setState({ limits: data, @@ -303,29 +303,24 @@ class MakerPage extends Component { handleCreateOfferButtonPressed = () => { this.state.amount == null ? this.setState({ amount: 0 }) : null; - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - type: this.state.type, - currency: this.state.currency, - amount: this.state.has_range ? null : this.state.amount, - has_range: this.state.enableAmountRange, - min_amount: this.state.minAmount, - max_amount: this.state.maxAmount, - payment_method: - this.state.payment_method === '' ? this.defaultPaymentMethod : this.state.payment_method, - is_explicit: this.state.is_explicit, - premium: this.state.is_explicit ? null : this.state.premium == '' ? 0 : this.state.premium, - satoshis: this.state.is_explicit ? this.state.satoshis : null, - public_duration: this.state.publicDuration, - escrow_duration: this.state.escrowDuration, - bond_size: this.state.bondSize, - bondless_taker: this.state.allowBondless, - }), + const body = { + type: this.state.type, + currency: this.state.currency, + amount: this.state.has_range ? null : this.state.amount, + has_range: this.state.enableAmountRange, + min_amount: this.state.minAmount, + max_amount: this.state.maxAmount, + payment_method: + this.state.payment_method === '' ? this.defaultPaymentMethod : this.state.payment_method, + is_explicit: this.state.is_explicit, + premium: this.state.is_explicit ? null : this.state.premium == '' ? 0 : this.state.premium, + satoshis: this.state.is_explicit ? this.state.satoshis : null, + public_duration: this.state.publicDuration, + escrow_duration: this.state.escrowDuration, + bond_size: this.state.bondSize, + bondless_taker: this.state.allowBondless, }; - fetch('/api/make/', requestOptions) - .then((response) => response.json()) + apiClient.post('/api/make/', body) .then( (data) => this.setState({ badRequest: data.bad_request }) & diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index 6e6d0cba..f40a9d2f 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -54,6 +54,7 @@ import { getCookie } from '../utils/cookies'; import { pn } from '../utils/prettyNumbers'; import { copyToClipboard } from '../utils/clipboard'; import { getWebln } from '../utils/webln'; +import { apiClient } from '../services/api'; class OrderPage extends Component { constructor(props) { @@ -122,8 +123,7 @@ class OrderPage extends Component { getOrderDetails = (id) => { this.setState({ orderId: id }); - fetch('/api/order' + '?order_id=' + id) - .then((response) => response.json()) + apiClient.get('/api/order/?order_id=' + id) .then(this.orderDetailsReceived); }; @@ -185,17 +185,10 @@ class OrderPage extends Component { }; sendWeblnInvoice = (invoice) => { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'update_invoice', - invoice, - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then((data) => this.completeSetState(data)); + apiClient.post('/api/order/?order_id=' + this.state.orderId, { + action: 'update_invoice', + invoice, + }).then((data) => this.completeSetState(data)); }; // Countdown Renderer callback with condition @@ -429,16 +422,10 @@ class OrderPage extends Component { takeOrder = () => { this.setState({ loading: true }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'take', - amount: this.state.takeAmount, - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) + apiClient.post('/api/order/?order_id=' + this.state.orderId, { + action: 'take', + amount: this.state.takeAmount, + }) .then((data) => this.handleWebln(data) & this.completeSetState(data)); }; @@ -454,16 +441,9 @@ class OrderPage extends Component { handleClickConfirmCancelButton = () => { this.setState({ loading: true }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'cancel', - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); + apiClient.post('/api/order/?order_id=' + this.state.orderId, { + action: 'cancel', + }).then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); this.handleClickCloseConfirmCancelDialog(); }; @@ -561,16 +541,9 @@ class OrderPage extends Component { }; handleClickConfirmCollaborativeCancelButton = () => { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'cancel', - }), - }; - fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions) - .then((response) => response.json()) - .then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); + apiClient.post('/api/order/?order_id=' + this.state.orderId, { + action: 'cancel', + }).then(() => this.getOrderDetails(this.state.orderId) & this.setState({ status: 4 })); this.handleClickCloseCollaborativeCancelDialog(); }; diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index 802804be..91fee744 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -33,6 +33,7 @@ import Chat from './EncryptedChat'; import TradeSummary from './TradeSummary'; import MediaQuery from 'react-responsive'; import { copyToClipboard } from '../utils/clipboard'; +import { apiClient } from '../services/api'; // Icons import PercentIcon from '@mui/icons-material/Percent'; @@ -132,16 +133,9 @@ class TradeBox extends Component { }; handleClickAgreeDisputeButton = () => { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'dispute', - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then((data) => this.props.completeSetState(data)); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'dispute', + }).then((data) => this.props.completeSetState(data)); this.handleClickCloseConfirmDispute(); }; @@ -486,16 +480,9 @@ class TradeBox extends Component { handleClickPauseOrder = () => { this.props.completeSetState({ pauseLoading: true }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'pause', - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then((data) => this.props.getOrderDetails(data.id)); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'pause', + }).then((data) => this.props.getOrderDetails(data.id)); }; showMakerWait = () => { @@ -639,17 +626,10 @@ class TradeBox extends Component { handleClickSubmitInvoiceButton = () => { this.setState({ badInvoice: false }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'update_invoice', - invoice: this.state.invoice, - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then( + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'update_invoice', + invoice: this.state.invoice, + }).then( (data) => this.setState({ badInvoice: data.bad_invoice }) & this.props.completeSetState(data), ); @@ -676,18 +656,11 @@ class TradeBox extends Component { handleClickSubmitAddressButton = () => { this.setState({ badInvoice: false }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'update_address', - address: this.state.address, - mining_fee_rate: Math.max(1, this.state.miningFee), - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then( + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'update_address', + address: this.state.address, + mining_fee_rate: Math.max(1, this.state.miningFee), + }).then( (data) => this.setState({ badAddress: data.bad_address }) & this.props.completeSetState(data), ); @@ -703,20 +676,13 @@ class TradeBox extends Component { handleClickSubmitStatementButton = () => { this.setState({ badInvoice: false }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'submit_statement', - statement: this.state.statement, - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then( - (data) => - this.setState({ badStatement: data.bad_statement }) & this.props.completeSetState(data), - ); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'submit_statement', + statement: this.state.statement, + }).then( + (data) => + this.setState({ badStatement: data.bad_statement }) & this.props.completeSetState(data), + ); }; handleScan = (data) => { @@ -1213,30 +1179,16 @@ class TradeBox extends Component { } handleClickConfirmButton = () => { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'confirm', - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then((data) => this.props.completeSetState(data)); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'confirm', + }).then((data) => this.props.completeSetState(data)); }; handleRatingUserChange = (e) => { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'rate_user', - rating: e.target.value, - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then((data) => this.props.completeSetState(data)); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'rate_user', + rating: e.target.value, + }).then((data) => this.props.completeSetState(data)); }; handleRatingRobosatsChange = (e) => { @@ -1245,17 +1197,10 @@ class TradeBox extends Component { } this.setState({ rating_platform: e.target.value }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - action: 'rate_platform', - rating: e.target.value, - }), - }; - fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) - .then((response) => response.json()) - .then((data) => this.props.completeSetState(data)); + apiClient.post('/api/order/?order_id=' + this.props.data.id, { + action: 'rate_platform', + rating: e.target.value, + }).then((data) => this.props.completeSetState(data)); }; showFiatSentButton() { @@ -1355,28 +1300,23 @@ class TradeBox extends Component { handleRenewOrderButtonPressed = () => { this.setState({ renewLoading: true }); - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - type: this.props.data.type, - currency: this.props.data.currency, - amount: this.props.data.has_range ? null : this.props.data.amount, - has_range: this.props.data.has_range, - min_amount: this.props.data.min_amount, - max_amount: this.props.data.max_amount, - payment_method: this.props.data.payment_method, - is_explicit: this.props.data.is_explicit, - premium: this.props.data.is_explicit ? null : this.props.data.premium, - satoshis: this.props.data.is_explicit ? this.props.data.satoshis : null, - public_duration: this.props.data.public_duration, - escrow_duration: this.props.data.escrow_duration, - bond_size: this.props.data.bond_size, - bondless_taker: this.props.data.bondless_taker, - }), + const body = { + type: this.props.data.type, + currency: this.props.data.currency, + amount: this.props.data.has_range ? null : this.props.data.amount, + has_range: this.props.data.has_range, + min_amount: this.props.data.min_amount, + max_amount: this.props.data.max_amount, + payment_method: this.props.data.payment_method, + is_explicit: this.props.data.is_explicit, + premium: this.props.data.is_explicit ? null : this.props.data.premium, + satoshis: this.props.data.is_explicit ? this.props.data.satoshis : null, + public_duration: this.props.data.public_duration, + escrow_duration: this.props.data.escrow_duration, + bond_size: this.props.data.bond_size, + bondless_taker: this.props.data.bondless_taker, }; - fetch('/api/make/', requestOptions) - .then((response) => response.json()) + apiClient.post('/api/make/', body) .then( (data) => this.setState({ badRequest: data.bad_request }) & diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index 2a19c8ad..96dffd20 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -27,6 +27,7 @@ import { genKey } from '../utils/pgp'; import { getCookie, writeCookie, deleteCookie } from '../utils/cookies'; import { saveAsJson } from '../utils/saveFile'; import { copyToClipboard } from '../utils/clipboard'; +import { apiClient } from '../services/api/index'; class UserGenPage extends Component { constructor(props) { @@ -63,27 +64,20 @@ class UserGenPage extends Component { const strength = tokenStrength(token); const refCode = this.refCode; - const requestOptions = genKey(token).then(function (key) { + const requestBody = genKey(token).then(function (key) { return { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ - token_sha256: sha256(token), - public_key: key.publicKeyArmored, - encrypted_private_key: key.encryptedPrivateKeyArmored, - unique_values: strength.uniqueValues, - counts: strength.counts, - length: token.length, - ref_code: refCode, - }), + token_sha256: sha256(token), + public_key: key.publicKeyArmored, + encrypted_private_key: key.encryptedPrivateKeyArmored, + unique_values: strength.uniqueValues, + counts: strength.counts, + length: token.length, + ref_code: refCode, }; }); - console.log(requestOptions); - - requestOptions.then((options) => - fetch('/api/user/', options) - .then((response) => response.json()) + requestBody.then((body) => + apiClient.post('/api/user/', body) .then((data) => { console.log(data) & this.setState({ @@ -127,11 +121,7 @@ class UserGenPage extends Component { }; delGeneratedUser() { - const requestOptions = { - method: 'DELETE', - headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - }; - fetch('/api/user', requestOptions).then((response) => response.json()); + apiClient.delete('/api/user') deleteCookie('sessionid'); deleteCookie('robot_token'); diff --git a/frontend/src/services/api/ApiWebClient/index.ts b/frontend/src/services/api/ApiWebClient/index.ts new file mode 100644 index 00000000..70caabae --- /dev/null +++ b/frontend/src/services/api/ApiWebClient/index.ts @@ -0,0 +1,40 @@ +import { ApiClient } from "../api" +import { getCookie } from '../../../utils/cookies'; + +class ApiWebClient implements ApiClient { + private getHeaders: () => HeadersInit = () => { + return { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') || "" } + } + + public post: (path: string, body: object) => Promise = (path, body) => { + const requestOptions = { + method: 'POST', + headers: this.getHeaders(), + body: JSON.stringify(body), + }; + return fetch(path, requestOptions).then((response) => response.json()) + } + + public put: (path: string, body: object) => Promise = (path, body) => { + const requestOptions = { + method: 'PUT', + headers: this.getHeaders(), + body: JSON.stringify(body), + }; + return fetch(path, requestOptions).then((response) => response.json()) + } + + public delete: (path: string) => Promise = (path) => { + const requestOptions = { + method: 'DELETE', + headers: this.getHeaders(), + }; + return fetch(path, requestOptions).then((response) => response.json()) + } + + public get: (path: string) => Promise = (path) => { + return fetch(path).then((response) => response.json()) + } +} + +export default ApiWebClient diff --git a/frontend/src/services/api/index.ts b/frontend/src/services/api/index.ts new file mode 100644 index 00000000..c0d5cde3 --- /dev/null +++ b/frontend/src/services/api/index.ts @@ -0,0 +1,10 @@ +import ApiWebClient from './ApiWebClient' + +export interface ApiClient { + post: (path: string, body: object) => Promise + put: (path: string, body: object) => Promise + get: (path: string) => Promise + delete: (path: string) => Promise +} + +export const apiClient: ApiClient = new ApiWebClient()