mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
Adding checks for ok status code in responses (#2178)
* fix: checks for ok status code in responses * refactor: replacing native fetch with useSWR * chore: remove url from logs * chore: add phoenixd error logs for non-success responses * chore: add use currencies hook * chore: use loading from currencies hook and filter out btc --------- Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
This commit is contained in:
parent
895edabba7
commit
f2ea668df8
9 changed files with 205 additions and 67 deletions
|
|
@ -1273,6 +1273,15 @@ func (api *api) RequestMempoolApi(ctx context.Context, endpoint string) (interfa
|
|||
return nil, errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"endpoint": endpoint,
|
||||
"status_code": res.StatusCode,
|
||||
"body": string(body),
|
||||
}).Error("Mempool endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("mempool endpoint returned non-success code: %s", string(body))
|
||||
}
|
||||
|
||||
var jsonContent interface{}
|
||||
jsonErr := json.Unmarshal(body, &jsonContent)
|
||||
if jsonErr != nil {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,15 @@ func (api *api) RequestEsploraApi(ctx context.Context, endpoint string) (interfa
|
|||
return nil, errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"endpoint": endpoint,
|
||||
"status_code": res.StatusCode,
|
||||
"body": string(body),
|
||||
}).Error("Esplora endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("esplora endpoint returned non-success code: %s", string(body))
|
||||
}
|
||||
|
||||
var jsonContent interface{}
|
||||
jsonErr := json.Unmarshal(body, &jsonContent)
|
||||
if jsonErr != nil {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func (api *api) RebalanceChannel(ctx context.Context, rebalanceChannelRequest *R
|
|||
return nil, errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
if res.StatusCode >= 300 {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
"body": string(body),
|
||||
|
|
|
|||
|
|
@ -25,3 +25,5 @@ export const DEFAULT_APP_BUDGET_RENEWAL = "monthly";
|
|||
|
||||
export const BITCOIN_DISPLAY_FORMAT_BIP177 = "bip177";
|
||||
export const BITCOIN_DISPLAY_FORMAT_SATS = "sats";
|
||||
|
||||
export const RATES_API_URL = "https://getalby.com/api/rates";
|
||||
|
|
|
|||
58
frontend/src/hooks/useCurrencies.ts
Normal file
58
frontend/src/hooks/useCurrencies.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import React from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { RATES_API_URL } from "src/constants";
|
||||
import { handleRequestError } from "src/utils/handleRequestError";
|
||||
|
||||
const albyRatesFetcher = (url: string) =>
|
||||
fetch(url).then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch currencies: ${res.status}`);
|
||||
}
|
||||
return res.json() as Promise<
|
||||
Record<string, { name: string; priority: number }>
|
||||
>;
|
||||
});
|
||||
|
||||
export function useCurrencies(includeSats = false) {
|
||||
const { data: ratesData, isLoading } = useSWR<
|
||||
Record<string, { name: string; priority: number }>
|
||||
>(RATES_API_URL, albyRatesFetcher, {
|
||||
onError: (error) => handleRequestError("Failed to fetch currencies", error),
|
||||
});
|
||||
|
||||
const currencies = React.useMemo(() => {
|
||||
if (!ratesData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (includeSats) {
|
||||
return [
|
||||
["SATS", "sats"],
|
||||
...Object.entries(ratesData)
|
||||
.filter(([code]) => code !== "BTC")
|
||||
.sort((a, b) => {
|
||||
const priorityDiff = a[1].priority - b[1].priority;
|
||||
return priorityDiff !== 0 ? priorityDiff : a[0].localeCompare(b[0]);
|
||||
})
|
||||
.map(([code, details]): [string, string] => [
|
||||
code.toUpperCase(),
|
||||
details.name,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
return Object.entries(ratesData)
|
||||
.filter(([code]) => code !== "BTC")
|
||||
.map(([code, details]): [string, string] => [
|
||||
code.toUpperCase(),
|
||||
details.name,
|
||||
])
|
||||
.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
}, [ratesData, includeSats]);
|
||||
|
||||
return {
|
||||
currencies,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
CardTitle,
|
||||
} from "src/components/ui/card";
|
||||
import { useApps } from "src/hooks/useApps";
|
||||
import { useCurrencies } from "src/hooks/useCurrencies";
|
||||
import { createApp } from "src/requests/createApp";
|
||||
import { CreateAppRequest, UpdateAppRequest } from "src/types";
|
||||
import { handleRequestError } from "src/utils/handleRequestError";
|
||||
|
|
@ -114,39 +115,13 @@ export function ZapPlanner() {
|
|||
const [frequencyValue, setFrequencyValue] = React.useState("1");
|
||||
const [frequencyUnit, setFrequencyUnit] = React.useState("months");
|
||||
const [currency, setCurrency] = React.useState<string>("USD");
|
||||
const [currencies, setCurrencies] = React.useState<string[]>([]);
|
||||
const { currencies, isLoading: isCurrenciesLoading } = useCurrencies(true);
|
||||
|
||||
const [convertedAmount, setConvertedAmount] = React.useState<string>("");
|
||||
const [satoshiAmount, setSatoshiAmount] = React.useState<number | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
// fetch the fiat list and prepend sats/BTC
|
||||
async function fetchCurrencies() {
|
||||
try {
|
||||
const res = await fetch("https://getalby.com/api/rates");
|
||||
const data: Record<string, { name: string; priority: number }> =
|
||||
await res.json();
|
||||
const fiatCodes = Object.keys(data)
|
||||
// drop "BTC" - ZapPlanner uses SATS for the bitcoin currency
|
||||
.filter((code) => code !== "BTC")
|
||||
.sort((a, b) => {
|
||||
const priorityDiff = data[a].priority - data[b].priority;
|
||||
if (priorityDiff !== 0) {
|
||||
return priorityDiff;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
})
|
||||
.map((c) => c.toUpperCase());
|
||||
setCurrencies(["SATS", ...fiatCodes]);
|
||||
} catch (err) {
|
||||
console.error("Failed to load currencies", err);
|
||||
}
|
||||
}
|
||||
fetchCurrencies();
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
// reset form on close
|
||||
if (!open) {
|
||||
|
|
@ -164,6 +139,10 @@ export function ZapPlanner() {
|
|||
}, [open]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isCurrenciesLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If amount is empty, clear conversion output
|
||||
if (!amount) {
|
||||
setConvertedAmount("");
|
||||
|
|
@ -200,7 +179,7 @@ export function ZapPlanner() {
|
|||
};
|
||||
|
||||
convertCurrency();
|
||||
}, [amount, currency, open]);
|
||||
}, [amount, currency, open, isCurrenciesLoading]);
|
||||
|
||||
const appStoreApp = appStoreApps.find((app) => app.id === "zapplanner");
|
||||
if (!appStoreApp) {
|
||||
|
|
@ -428,14 +407,24 @@ export function ZapPlanner() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<Select value={currency} onValueChange={setCurrency}>
|
||||
<Select
|
||||
value={currency}
|
||||
onValueChange={setCurrency}
|
||||
disabled={isCurrenciesLoading}
|
||||
>
|
||||
<SelectTrigger className="w-1/2">
|
||||
<SelectValue />
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isCurrenciesLoading
|
||||
? "Loading currencies..."
|
||||
: "Select a currency"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{currencies.map((code) => (
|
||||
{currencies.map(([code]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{code === "BTC" ? "BTC (sats)" : code}
|
||||
{code}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { StarsIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import Loading from "src/components/Loading";
|
||||
import SettingsHeader from "src/components/SettingsHeader";
|
||||
|
|
@ -23,6 +22,7 @@ import {
|
|||
BITCOIN_DISPLAY_FORMAT_SATS,
|
||||
} from "src/constants";
|
||||
import { useAlbyMe } from "src/hooks/useAlbyMe";
|
||||
import { useCurrencies } from "src/hooks/useCurrencies";
|
||||
import { useInfo } from "src/hooks/useInfo";
|
||||
import { cn } from "src/lib/utils";
|
||||
import { handleRequestError } from "src/utils/handleRequestError";
|
||||
|
|
@ -31,33 +31,10 @@ import { request } from "src/utils/request";
|
|||
function Settings() {
|
||||
const { data: albyMe } = useAlbyMe();
|
||||
const { theme, darkMode, setTheme, setDarkMode } = useTheme();
|
||||
|
||||
const [fiatCurrencies, setFiatCurrencies] = useState<[string, string][]>([]);
|
||||
const { currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
|
||||
const { data: info, mutate: reloadInfo } = useInfo();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchCurrencies() {
|
||||
try {
|
||||
const response = await fetch(`https://getalby.com/api/rates`);
|
||||
const data: Record<string, { name: string }> = await response.json();
|
||||
|
||||
const mappedCurrencies: [string, string][] = Object.entries(data).map(
|
||||
([code, details]) => [code.toUpperCase(), details.name]
|
||||
);
|
||||
|
||||
mappedCurrencies.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
setFiatCurrencies(mappedCurrencies);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
handleRequestError("Failed to fetch currencies", error);
|
||||
}
|
||||
}
|
||||
|
||||
fetchCurrencies();
|
||||
}, []);
|
||||
|
||||
async function updateSettings(
|
||||
payload: Record<string, string | boolean>,
|
||||
successMessage: string,
|
||||
|
|
@ -206,12 +183,22 @@ function Settings() {
|
|||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label htmlFor="currency">Fiat Currency</Label>
|
||||
<Select value={info?.currency} onValueChange={updateCurrency}>
|
||||
<Select
|
||||
value={info?.currency}
|
||||
onValueChange={updateCurrency}
|
||||
disabled={isCurrenciesLoading}
|
||||
>
|
||||
<SelectTrigger className="w-full md:w-60">
|
||||
<SelectValue placeholder="Select a currency" />
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isCurrenciesLoading
|
||||
? "Loading currencies..."
|
||||
: "Select a currency"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fiatCurrencies.map(([code, name]) => (
|
||||
{currencies.map(([code, name]) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{name} ({code})
|
||||
</SelectItem>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
|
@ -105,8 +107,20 @@ func (svc *PhoenixService) GetBalances(ctx context.Context, includeInactiveChann
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"body": string(body),
|
||||
"status_code": resp.StatusCode,
|
||||
}).Error("phoenixd get balance endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("phoenixd get balance endpoint returned non-success code: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var balanceRes BalanceResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&balanceRes); err != nil {
|
||||
if err := json.Unmarshal(body, &balanceRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -145,8 +159,20 @@ func fetchNodeInfo(ctx context.Context, svc *PhoenixService) (info *lnclient.Nod
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"body": string(body),
|
||||
"status_code": resp.StatusCode,
|
||||
}).Error("phoenixd get info endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("phoenixd get info endpoint returned non-success code: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var infoRes InfoResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&infoRes); err != nil {
|
||||
if err := json.Unmarshal(body, &infoRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lnclient.NodeInfo{
|
||||
|
|
@ -199,8 +225,20 @@ func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, descri
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"body": string(body),
|
||||
"status_code": resp.StatusCode,
|
||||
}).Error("phoenixd create invoice endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("phoenixd create invoice endpoint returned non-success code: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var invoiceRes MakeInvoiceResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&invoiceRes); err != nil {
|
||||
if err := json.Unmarshal(body, &invoiceRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -238,8 +276,20 @@ func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"body": string(body),
|
||||
"status_code": resp.StatusCode,
|
||||
}).Error("phoenixd incoming payments endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("phoenixd incoming payments endpoint returned non-success code: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var invoiceRes InvoiceResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&invoiceRes); err != nil {
|
||||
if err := json.Unmarshal(body, &invoiceRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -271,8 +321,16 @@ func (svc *PhoenixService) SendPaymentSync(payReq string, amount *uint64) (*lncl
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("phoenixd /payinvoice returned non-success status: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var payRes PayResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payRes); err != nil {
|
||||
if err := json.Unmarshal(body, &payRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -312,8 +370,16 @@ func (svc *PhoenixService) GetNodeConnectionInfo(ctx context.Context) (nodeConne
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("phoenixd /getinfo returned non-success status: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var infoRes InfoResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&infoRes); err != nil {
|
||||
if err := json.Unmarshal(body, &infoRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lnclient.NodeConnectionInfo{
|
||||
|
|
|
|||
|
|
@ -1415,6 +1415,15 @@ func (svc *swapsService) doMempoolRequest(endpoint string, result interface{}) e
|
|||
return errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"endpoint": endpoint,
|
||||
"status_code": res.StatusCode,
|
||||
"body": string(body),
|
||||
}).Error("Swaps mempool API endpoint returned non-success code")
|
||||
return fmt.Errorf("swaps mempool API endpoint returned non-success code: %s", string(body))
|
||||
}
|
||||
|
||||
jsonErr := json.Unmarshal(body, &result)
|
||||
if jsonErr != nil {
|
||||
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
||||
|
|
@ -1560,6 +1569,15 @@ func (svc *swapsService) getNextUnusedAddressFromXpub() (string, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"endpoint": endpoint,
|
||||
"status_code": res.StatusCode,
|
||||
"body": string(body),
|
||||
}).Error("Swaps esplora endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("swaps esplora endpoint returned non-success code: %s", string(body))
|
||||
}
|
||||
|
||||
var jsonContent interface{}
|
||||
err = json.Unmarshal(body, &jsonContent)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue