timeout = max( 1, $timeout ); $this->maxAttempts = max( 1, $maxAttempts ); $this->baseDelayMs = max( 0, $baseDelayMs ); } public function postJson( string $url, array $headers, string $body ): Result { return $this->request( 'POST', $url, $headers, $body ); } public function putJson( string $url, array $headers, string $body ): Result { return $this->request( 'PUT', $url, $headers, $body ); } public function getJson( string $url, array $headers ): Result { return $this->request( 'GET', $url, $headers, null ); } public function delete( string $url, array $headers ): Result { return $this->request( 'DELETE', $url, $headers, null ); } private function request( string $method, string $url, array $headers, ?string $body ): Result { $args = [ 'method' => $method, 'headers' => $headers, 'timeout' => $this->timeout, 'redirection' => 0, 'blocking' => true, 'sslverify' => true, 'data_format' => 'body', ]; if ( $body !== null ) { $args['body'] = $body; } $attempt = 0; $lastError = ''; $lastStatus = 0; $lastBody = ''; while ( $attempt < $this->maxAttempts ) { $attempt++; $response = \wp_remote_request( $url, $args ); if ( \is_wp_error( $response ) ) { $lastError = (string) $response->get_error_message(); if ( $attempt >= $this->maxAttempts ) { return Result::failure( 0, sprintf( /* translators: %s: HTTP transport error message */ \__( 'Network error: %s', 'agent001-support-assistant-for-woocommerce' ), $lastError ) ); } $this->backoff( $attempt ); continue; } $lastStatus = (int) \wp_remote_retrieve_response_code( $response ); $lastBody = (string) \wp_remote_retrieve_body( $response ); if ( $lastStatus >= 200 && $lastStatus < 300 ) { return Result::success( $lastStatus, $lastBody ); } if ( ! $this->shouldRetry( $lastStatus ) || $attempt >= $this->maxAttempts ) { $message = sprintf( /* translators: %d: HTTP status code returned by the remote */ \__( 'Remote returned HTTP %d.', 'agent001-support-assistant-for-woocommerce' ), $lastStatus ); $remoteError = $this->remoteError( $lastBody ); return Result::failure( $lastStatus, $remoteError !== '' ? $message . ' ' . $remoteError : $message ); } $this->backoff( $attempt ); } return Result::failure( $lastStatus, $lastError !== '' ? $lastError : \__( 'HTTP request failed.', 'agent001-support-assistant-for-woocommerce' ) ); } private function remoteError( string $body ): string { $decoded = json_decode( $body, true ); if ( ! is_array( $decoded ) || ! isset( $decoded['error'] ) || ! is_string( $decoded['error'] ) ) { return ''; } return substr( \sanitize_text_field( $decoded['error'] ), 0, 300 ); } private function shouldRetry( int $statusCode ): bool { if ( $statusCode >= 500 && $statusCode < 600 ) { return true; } return $statusCode === 408 || $statusCode === 429; } private function backoff( int $attempt ): void { $delay = (int) min( 30000, $this->baseDelayMs * ( 2 ** ( $attempt - 1 ) ) ); usleep( $delay * 1000 ); } }