@@ -14,6 +14,7 @@ import (
1414 "crypto/tls"
1515 "database/sql"
1616 "encoding/json"
17+ "errors"
1718 "fmt"
1819 "io"
1920 "net/http"
@@ -32,6 +33,12 @@ type ElabftwInfo struct {
3233 Raw map [string ]any `json:"raw"`
3334}
3435
36+ type elabftwErrorResponse struct {
37+ Code int `json:"code"`
38+ Message string `json:"message"`
39+ Description string `json:"description"`
40+ }
41+
3542func (a * App ) loadElabftwClientConfig (profileUUID string , instanceID int64 ) (* elabftwClientConfig , error ) {
3643 profileUUID , err := a .requireUnlockedProfile (profileUUID )
3744 if err != nil {
@@ -91,18 +98,24 @@ func (a *App) loadElabftwClientConfig(profileUUID string, instanceID int64) (*el
9198 return & cfg , nil
9299}
93100
94- func elabftwHTTPClient (verifyTLS bool ) * http.Client {
101+ func elabftwHTTPClient (verifyTLS bool , longTimeout bool ) * http.Client {
95102 transport := & http.Transport {
96103 TLSClientConfig : & tls.Config {
97104 // Only false when the user explicitly disables TLS verification.
98105 InsecureSkipVerify : ! verifyTLS ,
99106 },
100107 }
101108
102- // timeout prevents the desktop app from hanging forever if the server is unreachable
103- // Transport carries our TLS configuration, including whether to verify certificates
109+ // regular API requests should fail quickly if the server is unreachable
110+ // but Uploads get a much longer timeout because the deadline covers the entire
111+ // request, including sending the file, which may take several minutes on
112+ // slower connections
113+ timeout := 30 * time .Second // 30 sec
114+ if longTimeout {
115+ timeout = 10 * time .Minute // 10 mins
116+ }
104117 return & http.Client {
105- Timeout : 30 * time . Second ,
118+ Timeout : timeout ,
106119 Transport : transport ,
107120 }
108121}
@@ -111,18 +124,17 @@ func (a *App) elabftwRequest(
111124 profileUUID string ,
112125 instanceID int64 ,
113126 method string ,
114- apiPath string ,
127+ path string ,
115128 body io.Reader ,
129+ isUpload bool ,
130+ headers ... map [string ]string ,
116131) (* http.Response , error ) {
117132 cfg , err := a .loadElabftwClientConfig (profileUUID , instanceID )
118133 if err != nil {
119134 return nil , err
120135 }
121136
122- apiPath = "/" + strings .TrimLeft (apiPath , "/" )
123- url := elabftwAPIBaseURL (cfg .SiteURL ) + apiPath
124-
125- req , err := http .NewRequest (method , url , body )
137+ req , err := http .NewRequest (method , elabftwAPIBaseURL (cfg .SiteURL )+ path , body )
126138 if err != nil {
127139 return nil , fmt .Errorf ("create elabftw request: %w" , err )
128140 }
@@ -134,9 +146,17 @@ func (a *App) elabftwRequest(
134146 req .Header .Set ("Content-Type" , "application/json" )
135147 }
136148
137- resp , err := elabftwHTTPClient (cfg .VerifyTLS ).Do (req )
149+ for _ , h := range headers {
150+ for k , v := range h {
151+ req .Header .Set (k , v )
152+ }
153+ }
154+
155+ client := elabftwHTTPClient (cfg .VerifyTLS , isUpload )
156+
157+ resp , err := client .Do (req )
138158 if err != nil {
139- return nil , fmt .Errorf ("call elabftw %s %s: %w" , method , apiPath , err )
159+ return nil , fmt .Errorf ("call elabftw %s %s: %w" , method , path , err )
140160 }
141161
142162 return resp , nil
@@ -156,11 +176,26 @@ func decodeElabftwJSONResponse(resp *http.Response, target any) error {
156176
157177 if resp .StatusCode < 200 || resp .StatusCode >= 300 {
158178 body , _ := io .ReadAll (io .LimitReader (resp .Body , 4096 ))
179+
180+ var apiErr elabftwErrorResponse
181+ if err := json .Unmarshal (body , & apiErr ); err == nil {
182+ // Prefer a detailed description if available.
183+ if apiErr .Description != "" {
184+ return errors .New (apiErr .Description )
185+ }
186+
187+ if apiErr .Message != "" {
188+ return errors .New (apiErr .Message )
189+ }
190+ }
191+
192+ // Fallback if the response isn't JSON.
159193 msg := strings .TrimSpace (string (body ))
160- if msg = = "" {
161- return fmt . Errorf ( "elabftw returned HTTP %d" , resp . StatusCode )
194+ if msg ! = "" {
195+ return errors . New ( msg )
162196 }
163- return fmt .Errorf ("elabftw returned HTTP %d: %s" , resp .StatusCode , msg )
197+
198+ return fmt .Errorf ("eLabFTW returned HTTP %d" , resp .StatusCode )
164199 }
165200
166201 if target == nil {
@@ -184,7 +219,7 @@ func jsonBody(v any) (*bytes.Reader, error) {
184219
185220/* ---------- INFO ENDPOINT ---------- */
186221func (a * App ) FetchElabftwInfo (profileUUID string , instanceID int64 ) (* ElabftwInfo , error ) {
187- resp , err := a .elabftwRequest (profileUUID , instanceID , http .MethodGet , "/info" , nil )
222+ resp , err := a .elabftwRequest (profileUUID , instanceID , http .MethodGet , "/info" , nil , false )
188223 if err != nil {
189224 return nil , err
190225 }
0 commit comments