diff --git a/agent/brain.go b/agent/brain.go index e3961d49..c3c267c9 100644 --- a/agent/brain.go +++ b/agent/brain.go @@ -3,7 +3,6 @@ package agent import ( "encoding/json" "fmt" - "io" "log/slog" "net/http" "nofx/safe" @@ -87,7 +86,7 @@ func (b *Brain) scanNews(seen map[string]bool) { b.logger.Debug("news API non-200", "status", resp.StatusCode) return } - body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) // 1MB limit + body, err := safe.ReadAllLimited(resp.Body, 1024*1024) // 1MB limit if err != nil { return } var result struct { @@ -167,7 +166,7 @@ func (b *Brain) sendBrief(hour int) { for _, sym := range []string{"BTCUSDT", "ETHUSDT"} { resp, err := b.http.Get(fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", sym)) if err != nil { continue } - body, readErr := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) // 64KB limit + body, readErr := safe.ReadAllLimited(resp.Body, 64*1024) // 64KB limit statusOK := resp.StatusCode == http.StatusOK resp.Body.Close() if readErr != nil || !statusOK { continue } diff --git a/agent/sentinel.go b/agent/sentinel.go index 186dbc56..3c5f0f22 100644 --- a/agent/sentinel.go +++ b/agent/sentinel.go @@ -3,7 +3,6 @@ package agent import ( "encoding/json" "fmt" - "io" "log/slog" "math" "net/http" @@ -120,7 +119,7 @@ func (s *Sentinel) check(symbol string) { s.logger.Debug("sentinel ticker non-200", "symbol", symbol, "status", resp.StatusCode) return } - body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) // 256KB limit + body, err := safe.ReadAllLimited(resp.Body, 256*1024) // 256KB limit if err != nil { return } var t map[string]interface{} if err := json.Unmarshal(body, &t); err != nil { return } diff --git a/trader/bybit/order_sync.go b/trader/bybit/order_sync.go index 6d6a0835..d9539019 100644 --- a/trader/bybit/order_sync.go +++ b/trader/bybit/order_sync.go @@ -71,8 +71,7 @@ func (t *BybitTrader) getTradesViaHTTP(startTime time.Time, limit int) ([]BybitT req.Header.Set("X-BAPI-RECV-WINDOW", recvWindow) req.Header.Set("Content-Type", "application/json") - // Use http.DefaultClient for the request - resp, err := http.DefaultClient.Do(req) + resp, err := bybitHTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to call Bybit API: %w", err) } diff --git a/trader/bybit/trader.go b/trader/bybit/trader.go index 631fbab9..e0c5f4b9 100644 --- a/trader/bybit/trader.go +++ b/trader/bybit/trader.go @@ -15,6 +15,17 @@ import ( bybit "github.com/bybit-exchange/bybit.go.api" ) +// bybitHTTPClient is a shared HTTP client for direct Bybit API calls (public + signed). +// Reused across requests to benefit from connection pooling; 30s timeout prevents hangs. +var bybitHTTPClient = &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 20, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + }, +} + // BybitTrader Bybit USDT Perpetual Futures Trader type BybitTrader struct { client *bybit.Client @@ -94,7 +105,7 @@ func (t *BybitTrader) getQtyStep(symbol string) float64 { // Call public API directly to get contract information url := fmt.Sprintf("https://api.bybit.com/v5/market/instruments-info?category=linear&symbol=%s", symbol) - resp, err := http.Get(url) + resp, err := bybitHTTPClient.Get(url) if err != nil { logger.Infof("⚠️ [Bybit] Failed to get precision info for %s: %v", symbol, err) return 1 // Default to integer diff --git a/trader/bybit/trader_account.go b/trader/bybit/trader_account.go index 464fb522..4ae0795b 100644 --- a/trader/bybit/trader_account.go +++ b/trader/bybit/trader_account.go @@ -127,8 +127,7 @@ func (t *BybitTrader) getClosedPnLViaHTTP(startTime time.Time, limit int) ([]typ req.Header.Set("X-BAPI-RECV-WINDOW", recvWindow) req.Header.Set("Content-Type", "application/json") - // Use http.DefaultClient for the request - resp, err := http.DefaultClient.Do(req) + resp, err := bybitHTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to call Bybit API: %w", err) } diff --git a/trader/bybit/trader_orders.go b/trader/bybit/trader_orders.go index fa600c95..73ed87a3 100644 --- a/trader/bybit/trader_orders.go +++ b/trader/bybit/trader_orders.go @@ -690,7 +690,7 @@ func (t *BybitTrader) GetOrderBook(symbol string, depth int) (bids, asks [][]flo // Use HTTP request directly since the SDK doesn't expose GetOrderbook url := fmt.Sprintf("https://api.bybit.com/v5/market/orderbook?category=linear&symbol=%s&limit=%d", symbol, depth) - resp, err := http.Get(url) + resp, err := bybitHTTPClient.Get(url) if err != nil { return nil, nil, fmt.Errorf("failed to get order book: %w", err) } diff --git a/web/src/contexts/AuthContext.tsx b/web/src/contexts/AuthContext.tsx index f7c60985..3132c68f 100644 --- a/web/src/contexts/AuthContext.tsx +++ b/web/src/contexts/AuthContext.tsx @@ -250,10 +250,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const resetPassword = async (email: string, newPassword: string) => { try { + const savedToken = token || localStorage.getItem('auth_token') const response = await fetch('/api/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json', + ...(savedToken ? { Authorization: `Bearer ${savedToken}` } : {}), }, body: JSON.stringify({ email, diff --git a/web/src/pages/SettingsPage.tsx b/web/src/pages/SettingsPage.tsx index d278e1ca..7a3c673f 100644 --- a/web/src/pages/SettingsPage.tsx +++ b/web/src/pages/SettingsPage.tsx @@ -64,7 +64,7 @@ export function SettingsPage() { method: 'PUT', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('token') || ''}`, + Authorization: `Bearer ${localStorage.getItem('auth_token') || ''}`, }, body: JSON.stringify({ new_password: newPassword }), })