@@ -59,6 +59,48 @@ func (c *Client) APIRequestJSON(ctx context.Context, method, uri string, queryPa
5959// retryFunc is the type for functions that can be retried.
6060type retryFunc [T any ] func (ctx context.Context , method , uri string , queryParams map [string ]string , bodyParams map [string ]any , needsKeys , needsAuth bool ) (T , error )
6161
62+ // handleRetryableError attempts to recover from an encryption or token error by refreshing credentials.
63+ // Returns true if the error was handled and a retry should be attempted.
64+ func handleRetryableError [T any ](
65+ ctx context.Context ,
66+ c * Client ,
67+ err error ,
68+ retryCount int ,
69+ ) (shouldRetry bool , retryErr error ) {
70+ var encErr * EncryptionError
71+ var tokenErr * TokenExpiredError
72+
73+ if errors .As (err , & encErr ) {
74+ // Retrieve new encryption keys and retry
75+ if err := c .GetEncryptionKeys (ctx ); err != nil {
76+ return false , fmt .Errorf ("failed to retrieve encryption keys: %w" , err )
77+ }
78+ // Apply backoff delay before retry
79+ backoff := calculateBackoff (retryCount + 1 )
80+ if err := c .sleepFunc (ctx , backoff ); err != nil {
81+ return false , err
82+ }
83+
84+ return true , nil
85+ }
86+
87+ if errors .As (err , & tokenErr ) {
88+ // Login again and retry
89+ if err := c .Login (ctx ); err != nil {
90+ return false , fmt .Errorf ("failed to login: %w" , err )
91+ }
92+ // Apply backoff delay before retry
93+ backoff := calculateBackoff (retryCount + 1 )
94+ if err := c .sleepFunc (ctx , backoff ); err != nil {
95+ return false , err
96+ }
97+
98+ return true , nil
99+ }
100+
101+ return false , nil
102+ }
103+
62104// genericRetry implements the retry logic with exponential backoff for API requests.
63105// It handles encryption errors and token expiration by refreshing credentials and retrying.
64106func genericRetry [T any ](
@@ -97,31 +139,11 @@ func genericRetry[T any](
97139 response , err := executeFunc (ctx , method , uri , queryParams , bodyParams , needsKeys , needsAuth )
98140 if err != nil {
99141 // Handle retryable errors
100- var encErr * EncryptionError
101- var tokenErr * TokenExpiredError
102- if errors .As (err , & encErr ) {
103- // Retrieve new encryption keys and retry
104- if err := c .GetEncryptionKeys (ctx ); err != nil {
105- return zero , fmt .Errorf ("failed to retrieve encryption keys: %w" , err )
106- }
107- // Apply backoff delay before retry
108- backoff := calculateBackoff (retryCount + 1 )
109- if err := c .sleepFunc (ctx , backoff ); err != nil {
110- return zero , err
111- }
112-
113- return genericRetry (ctx , c , method , uri , queryParams , bodyParams , needsKeys , needsAuth , retryCount + 1 , executeFunc )
114- } else if errors .As (err , & tokenErr ) {
115- // Login again and retry
116- if err := c .Login (ctx ); err != nil {
117- return zero , fmt .Errorf ("failed to login: %w" , err )
118- }
119- // Apply backoff delay before retry
120- backoff := calculateBackoff (retryCount + 1 )
121- if err := c .sleepFunc (ctx , backoff ); err != nil {
122- return zero , err
123- }
124-
142+ shouldRetry , retryErr := handleRetryableError [T ](ctx , c , err , retryCount )
143+ if retryErr != nil {
144+ return zero , retryErr
145+ }
146+ if shouldRetry {
125147 return genericRetry (ctx , c , method , uri , queryParams , bodyParams , needsKeys , needsAuth , retryCount + 1 , executeFunc )
126148 }
127149
@@ -178,70 +200,98 @@ func handleAPIResponse(response *APIBaseResponse) (string, error) {
178200 return "" , NewAPIError ("Request failed for an unknown reason" )
179201}
180202
181- // executeAPIRequest handles the common logic for making API requests.
182- // It returns the encrypted payload string on success, or an error.
183- func (c * Client ) executeAPIRequest (ctx context.Context , method , uri string , queryParams map [string ]string , bodyParams map [string ]any , needsAuth bool ) (string , error ) {
184- timestamp := getTimestampStrMs ()
203+ // preparedParams holds the prepared and encrypted request parameters.
204+ type preparedParams struct {
205+ originalQueryStr string
206+ encryptedQueryParams url.Values
207+ originalBodyStr string
208+ encryptedBody string
209+ }
210+
211+ // prepareRequestParams encrypts query and body parameters for an API request.
212+ func (c * Client ) prepareRequestParams (queryParams map [string ]string , bodyParams map [string ]any ) (preparedParams , error ) {
213+ var params preparedParams
185214
186215 // Prepare query parameters (encrypted if provided)
187- originalQueryStr := ""
188- encryptedQueryParams := url.Values {}
189216 if len (queryParams ) > 0 {
190217 queryValues := url.Values {}
191218 for k , v := range queryParams {
192219 queryValues .Add (k , v )
193220 }
194- originalQueryStr = queryValues .Encode ()
221+ params . originalQueryStr = queryValues .Encode ()
195222
196- encrypted , err := c .encryptPayloadUsingKey (originalQueryStr )
223+ encrypted , err := c .encryptPayloadUsingKey (params . originalQueryStr )
197224 if err != nil {
198- return "" , fmt .Errorf ("failed to encrypt query params: %w" , err )
225+ return params , fmt .Errorf ("failed to encrypt query params: %w" , err )
199226 }
200- encryptedQueryParams .Add ("params" , encrypted )
227+ params .encryptedQueryParams = url.Values {}
228+ params .encryptedQueryParams .Add ("params" , encrypted )
201229 }
202230
203231 // Prepare body (encrypted if provided)
204- originalBodyStr := ""
205- encryptedBody := ""
206232 if len (bodyParams ) > 0 {
207233 bodyJSON , err := json .Marshal (bodyParams )
208234 if err != nil {
209- return "" , fmt .Errorf ("failed to marshal body params: %w" , err )
235+ return params , fmt .Errorf ("failed to marshal body params: %w" , err )
210236 }
211- originalBodyStr = string (bodyJSON )
237+ params . originalBodyStr = string (bodyJSON )
212238
213- encrypted , err := c .encryptPayloadUsingKey (originalBodyStr )
239+ encrypted , err := c .encryptPayloadUsingKey (params . originalBodyStr )
214240 if err != nil {
215- return "" , fmt .Errorf ("failed to encrypt body: %w" , err )
241+ return params , fmt .Errorf ("failed to encrypt body: %w" , err )
216242 }
217- encryptedBody = encrypted
243+ params . encryptedBody = encrypted
218244 }
219245
246+ return params , nil
247+ }
248+
249+ // calculateSignature determines the appropriate signature for the request.
250+ func (c * Client ) calculateSignature (method , uri , originalQueryStr , originalBodyStr , timestamp string ) string {
251+ switch {
252+ case uri == EndpointCheckVersion :
253+ return c .getSignFromTimestamp (timestamp )
254+ case method == http .MethodGet :
255+ return c .getSignFromPayloadAndTimestamp (originalQueryStr , timestamp )
256+ case method == http .MethodPost :
257+ return c .getSignFromPayloadAndTimestamp (originalBodyStr , timestamp )
258+ default :
259+ return ""
260+ }
261+ }
262+
263+ // buildHTTPRequest creates an HTTP request with all necessary headers.
264+ func (c * Client ) buildHTTPRequest (ctx context.Context , method , uri , timestamp string , params preparedParams , needsAuth bool ) (* http.Request , error ) {
220265 // Build URL
221266 requestURL := c .baseURL + uri
222- if len (encryptedQueryParams ) > 0 {
223- requestURL += "?" + encryptedQueryParams .Encode ()
267+ if len (params . encryptedQueryParams ) > 0 {
268+ requestURL += "?" + params . encryptedQueryParams .Encode ()
224269 }
225270
226271 // Create request with context
227272 var req * http.Request
228273 var err error
229- if encryptedBody != "" {
230- req , err = http .NewRequestWithContext (ctx , method , requestURL , bytes .NewBufferString (encryptedBody ))
274+ if params . encryptedBody != "" {
275+ req , err = http .NewRequestWithContext (ctx , method , requestURL , bytes .NewBufferString (params . encryptedBody ))
231276 } else {
232277 req , err = http .NewRequestWithContext (ctx , method , requestURL , nil )
233278 }
234279 if err != nil {
235- return "" , fmt .Errorf ("failed to create request: %w" , err )
280+ return nil , fmt .Errorf ("failed to create request: %w" , err )
236281 }
237282
238283 // Generate sensor data
239284 sensorData , err := c .sensorDataBuilder .GenerateSensorData ()
240285 if err != nil {
241- return "" , fmt .Errorf ("failed to generate sensor data: %w" , err )
286+ return nil , fmt .Errorf ("failed to generate sensor data: %w" , err )
242287 }
243288
244289 // Set headers
290+ accessToken := ""
291+ if needsAuth {
292+ accessToken = c .accessToken
293+ }
294+
245295 headers := map [string ]string {
246296 "device-id" : c .baseAPIDeviceID ,
247297 "app-code" : c .appCode ,
@@ -253,29 +303,35 @@ func (c *Client) executeAPIRequest(ctx context.Context, method, uri string, quer
253303 "timestamp" : timestamp ,
254304 "Content-Type" : "application/json" ,
255305 "X-acf-sensor-data" : sensorData ,
306+ "access-token" : accessToken ,
307+ "sign" : c .calculateSignature (method , uri , params .originalQueryStr , params .originalBodyStr , timestamp ),
256308 }
257309
258- if needsAuth {
259- headers ["access-token" ] = c .accessToken
260- } else {
261- headers ["access-token" ] = ""
310+ for k , v := range headers {
311+ req .Header .Set (k , v )
262312 }
263313
264- // Calculate signature
265- switch {
266- case uri == EndpointCheckVersion :
267- headers ["sign" ] = c .getSignFromTimestamp (timestamp )
268- case method == http .MethodGet :
269- headers ["sign" ] = c .getSignFromPayloadAndTimestamp (originalQueryStr , timestamp )
270- case method == http .MethodPost :
271- headers ["sign" ] = c .getSignFromPayloadAndTimestamp (originalBodyStr , timestamp )
272- }
314+ c .logRequest (method , requestURL , headers , params .originalBodyStr )
273315
274- for k , v := range headers {
275- req .Header .Set (k , v )
316+ return req , nil
317+ }
318+
319+ // executeAPIRequest handles the common logic for making API requests.
320+ // It returns the encrypted payload string on success, or an error.
321+ func (c * Client ) executeAPIRequest (ctx context.Context , method , uri string , queryParams map [string ]string , bodyParams map [string ]any , needsAuth bool ) (string , error ) {
322+ timestamp := getTimestampStrMs ()
323+
324+ // Prepare and encrypt parameters
325+ params , err := c .prepareRequestParams (queryParams , bodyParams )
326+ if err != nil {
327+ return "" , err
276328 }
277329
278- c .logRequest (method , requestURL , headers , originalBodyStr )
330+ // Build HTTP request with headers
331+ req , err := c .buildHTTPRequest (ctx , method , uri , timestamp , params , needsAuth )
332+ if err != nil {
333+ return "" , err
334+ }
279335
280336 // Send request
281337 resp , err := c .httpClient .Do (req )
0 commit comments