fix: add timeout to HTTP client (#835)

Daniel Purnomo created

Change summary

main.go | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)

Detailed changes

main.go 🔗

@@ -56,6 +56,18 @@ var (
 	version = "dev"
 	commit  = ""
 	date    = ""
+
+	// httpClient is used for all outbound HTTP requests (update checks, asset downloads).
+	// Configured with a 30s timeout to prevent indefinite hangs on slow/unresponsive servers.
+	httpClient = &http.Client{
+		Timeout: 30 * time.Second,
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			if len(via) >= 5 {
+				return fmt.Errorf("stopped after 5 redirects")
+			}
+			return nil
+		},
+	}
 )
 
 // UpdateAvailableMsg is sent into the TUI when a newer release is detected.
@@ -2784,7 +2796,7 @@ func checkForUpdatesCmd() tea.Cmd {
 	return func() tea.Msg {
 		// Non-fatal: if anything goes wrong we just don't show the update message.
 		const api = "https://api.github.com/repos/floatpane/matcha/releases/latest"
-		resp, err := http.Get(api)
+		resp, err := httpClient.Get(api)
 		if err != nil {
 			return nil
 		}
@@ -3028,7 +3040,7 @@ func isFlagSet(fs *flag.FlagSet, name string) bool {
 
 func runUpdateCLI() error {
 	const api = "https://api.github.com/repos/floatpane/matcha/releases/latest"
-	resp, err := http.Get(api)
+	resp, err := httpClient.Get(api)
 	if err != nil {
 		return fmt.Errorf("could not query releases: %w", err)
 	}
@@ -3166,7 +3178,7 @@ func runUpdateCLI() error {
 	fmt.Println("Downloading...")
 
 	// Download asset
-	respAsset, err := http.Get(assetURL)
+	respAsset, err := httpClient.Get(assetURL)
 	if err != nil {
 		return fmt.Errorf("download failed: %w", err)
 	}