@@ -12,16 +12,32 @@ import (
1212 "io"
1313 "net/http"
1414 "os"
15+ "strings"
1516
1617 "github.com/dydx/vico-cli/pkg/cache"
1718)
1819
1920// Error codes from the API
2021const (
21- ErrorAccountKicked = - 1024
22- ErrorTokenMissing = - 1025
22+ ErrorAccountKicked = - 1024 // Account has been kicked offline
23+ ErrorTokenMissing = - 1025 // Token is missing
24+ ErrorTokenInvalid = - 1026 // Token is invalid
25+ ErrorTokenExpired = - 1027 // Token has expired
2326)
2427
28+ // isDebugMode returns true if debug logging is enabled
29+ func isDebugMode () bool {
30+ debug := os .Getenv ("VICOHOME_DEBUG" )
31+ return debug == "true" || debug == "1"
32+ }
33+
34+ // logDebug prints a message only if debug mode is enabled
35+ func logDebug (format string , args ... interface {}) {
36+ if isDebugMode () {
37+ fmt .Fprintf (os .Stderr , format , args ... )
38+ }
39+ }
40+
2541// LoginRequest represents the JSON request body sent to the Vicohome API
2642// during authentication.
2743type LoginRequest struct {
@@ -55,16 +71,19 @@ func Authenticate() (string, error) {
5571 cacheManager , err := cache .NewTokenCacheManager ()
5672 if err != nil {
5773 // If we can't create a cache manager, fall back to direct authentication
74+ logDebug ("Warning: Could not create token cache manager: %v\n " , err )
5875 return authenticateDirectly ()
5976 }
6077
6178 token , valid := cacheManager .GetToken ()
6279 if valid {
80+ logDebug ("Using cached token\n " )
6381 // We have a valid cached token, return it
6482 return token , nil
6583 }
6684
6785 // No valid cached token, authenticate and cache the new token
86+ logDebug ("No valid cached token found, authenticating directly\n " )
6887 token , err = authenticateDirectly ()
6988 if err != nil {
7089 return "" , err
@@ -73,7 +92,9 @@ func Authenticate() (string, error) {
7392 // Cache the token for future use (24 hours validity)
7493 if err := cacheManager .SaveToken (token , 24 ); err != nil {
7594 // Non-fatal error, we can still return the token
76- fmt .Fprintf (os .Stderr , "Warning: failed to cache token: %v\n " , err )
95+ logDebug ("Warning: failed to cache token: %v\n " , err )
96+ } else {
97+ logDebug ("Successfully cached new token\n " )
7798 }
7899
79100 return token , nil
@@ -170,30 +191,80 @@ func authenticateDirectly() (string, error) {
170191// - bool: True if the token needs to be refreshed, false otherwise
171192// - error: Any error found in the response, or nil if no error was found
172193func ValidateResponse (respBody []byte ) (bool , error ) {
194+ // Check if we have a non-JSON response (probably HTML error page)
195+ if len (respBody ) > 0 && (respBody [0 ] == '<' || respBody [0 ] == '\r' || respBody [0 ] == '\n' ) {
196+ // Print a preview for debugging
197+ if isDebugMode () {
198+ preview := string (respBody )
199+ if len (preview ) > 100 {
200+ preview = preview [:100 ] + "..."
201+ }
202+ logDebug ("Warning: Received non-JSON response (likely auth issue): %s\n " , preview )
203+ }
204+ return true , fmt .Errorf ("received non-JSON response (likely authentication issue)" )
205+ }
206+
173207 // Try to parse the response
174208 var responseMap map [string ]interface {}
175209 if err := json .Unmarshal (respBody , & responseMap ); err != nil {
176210 return false , fmt .Errorf ("error unmarshaling response: %w" , err )
177211 }
178212
179- // Check for authentication errors
213+ // Print the response for debugging
214+ if isDebugMode () {
215+ prettyJSON , _ := json .MarshalIndent (responseMap , "" , " " )
216+ logDebug ("API Response: %s\n " , string (prettyJSON ))
217+ }
218+
219+ // Check for authentication errors - try both result and code fields (API inconsistency)
220+ var errorCode float64
221+ var errorMsg string
222+ var hasError bool
223+
224+ // Check the "result" field first (main API)
180225 if result , ok := responseMap ["result" ].(float64 ); ok {
226+ errorCode = result
181227 msg , _ := responseMap ["msg" ].(string )
228+ errorMsg = msg
229+ hasError = result != 0
230+ }
182231
183- // Check if we need to refresh the token
184- if result == ErrorAccountKicked || result == ErrorTokenMissing {
185- // Clear the cache
186- cacheManager , err := cache .NewTokenCacheManager ()
187- if err == nil {
188- cacheManager .ClearToken ()
189- }
190- return true , fmt .Errorf ("authentication error: %s (code: %.0f)" , msg , result )
232+ // Also check the "code" field (some endpoints use this instead)
233+ if code , ok := responseMap ["code" ].(float64 ); ok {
234+ errorCode = code
235+ msg , _ := responseMap ["msg" ].(string )
236+ errorMsg = msg
237+ hasError = code != 0
238+ }
239+
240+ // If we found an error code
241+ if hasError {
242+ // Check if it's an auth error requiring token refresh
243+ isAuthError := errorCode == ErrorAccountKicked ||
244+ errorCode == ErrorTokenMissing ||
245+ errorCode == ErrorTokenInvalid ||
246+ errorCode == ErrorTokenExpired
247+
248+ // Also check message strings for auth-related errors
249+ if ! isAuthError && errorMsg != "" {
250+ errorMsgLower := strings .ToLower (errorMsg )
251+ isAuthError = strings .Contains (errorMsgLower , "token" ) ||
252+ strings .Contains (errorMsgLower , "auth" ) ||
253+ strings .Contains (errorMsgLower , "login" ) ||
254+ strings .Contains (errorMsgLower , "账号" ) || // account in Chinese
255+ strings .Contains (errorMsgLower , "踢下线" ) // kicked offline in Chinese
191256 }
192257
193- // Check for other API errors
194- if result != 0 {
195- return false , fmt .Errorf ("API error: %s (code: %.0f)" , msg , result )
258+ if isAuthError {
259+ if isDebugMode () {
260+ logDebug ("Auth error detected: %s (code: %.0f)\n " , errorMsg , errorCode )
261+ }
262+ // Don't clear cache here, let the caller handle it
263+ return true , fmt .Errorf ("authentication error: %s (code: %.0f)" , errorMsg , errorCode )
196264 }
265+
266+ // Otherwise it's a regular API error
267+ return false , fmt .Errorf ("API error: %s (code: %.0f)" , errorMsg , errorCode )
197268 }
198269
199270 return false , nil
@@ -213,6 +284,19 @@ func ValidateResponse(respBody []byte) (bool, error) {
213284func ExecuteWithRetry (req * http.Request ) ([]byte , error ) {
214285 // First attempt with current token
215286 client := & http.Client {}
287+
288+ // Make sure we can reuse the request body if needed
289+ var requestBodyBytes []byte
290+ if req .Body != nil {
291+ var err error
292+ requestBodyBytes , err = io .ReadAll (req .Body )
293+ if err != nil {
294+ return nil , fmt .Errorf ("error reading request body: %w" , err )
295+ }
296+ req .Body = io .NopCloser (bytes .NewBuffer (requestBodyBytes ))
297+ }
298+
299+ // First attempt
216300 resp , err := client .Do (req )
217301 if err != nil {
218302 return nil , fmt .Errorf ("error making request: %w" , err )
@@ -225,30 +309,57 @@ func ExecuteWithRetry(req *http.Request) ([]byte, error) {
225309 }
226310
227311 // Check if we need to refresh the token
228- needsRefresh , _ := ValidateResponse (respBody )
312+ needsRefresh , apiErr := ValidateResponse (respBody )
229313 if needsRefresh {
314+ // Only show detailed logs in debug mode
315+ logDebug ("Token refresh needed: %v\n " , apiErr )
316+
230317 // Clear the cache and get a new token
231318 cacheManager , err := cache .NewTokenCacheManager ()
232319 if err == nil {
233320 cacheManager .ClearToken ()
234321 }
235322
236- // Get a new token
323+ // Get a new token directly (bypass cache)
237324 token , err := authenticateDirectly ()
238325 if err != nil {
239326 return nil , fmt .Errorf ("failed to refresh token: %w" , err )
240327 }
241328
242- // Cache the new token
329+ // Cache the new token with verbose error handling
243330 if cacheManager != nil {
244- cacheManager .SaveToken (token , 24 )
331+ if err := cacheManager .SaveToken (token , 24 ); err != nil {
332+ logDebug ("Warning: failed to cache refreshed token: %v\n " , err )
333+ } else {
334+ logDebug ("Successfully refreshed and cached new token\n " )
335+ }
336+ }
337+
338+ // Create a new request with the same parameters but new token
339+ newReq , err := http .NewRequest (req .Method , req .URL .String (), nil )
340+ if err != nil {
341+ return nil , fmt .Errorf ("error creating request for retry: %w" , err )
342+ }
343+
344+ // Copy all headers from the original request
345+ for key , values := range req .Header {
346+ for _ , value := range values {
347+ newReq .Header .Add (key , value )
348+ }
245349 }
246350
247- // Update the request with the new token
248- req .Header .Set ("Authorization" , token )
351+ // Set the new Authorization header
352+ newReq .Header .Set ("Authorization" , token )
249353
250- // Retry the request
251- resp , err = client .Do (req )
354+ // Add back the body if there was one
355+ if len (requestBodyBytes ) > 0 {
356+ newReq .Body = io .NopCloser (bytes .NewBuffer (requestBodyBytes ))
357+ newReq .ContentLength = int64 (len (requestBodyBytes ))
358+ }
359+
360+ // Retry the request with the new token
361+ logDebug ("Retrying request with refreshed token\n " )
362+ resp , err = client .Do (newReq )
252363 if err != nil {
253364 return nil , fmt .Errorf ("error making request after token refresh: %w" , err )
254365 }
@@ -258,6 +369,13 @@ func ExecuteWithRetry(req *http.Request) ([]byte, error) {
258369 if err != nil {
259370 return nil , fmt .Errorf ("error reading response body after token refresh: %w" , err )
260371 }
372+
373+ // Check if we still got an error after refreshing the token
374+ needsRefresh2 , apiError := ValidateResponse (respBody )
375+ if needsRefresh2 || apiError != nil {
376+ // This is a critical error worth showing - authentication failed even after token refresh
377+ return nil , fmt .Errorf ("authentication failed even after token refresh: %v" , apiError )
378+ }
261379 }
262380
263381 return respBody , nil
0 commit comments