service_name, $this->userID, $endpoint, $data]; if (!$force) { $cached = $this->cache->get($key); if ($cached) { return $cached; } } $response = $this->makeRequest('GET', $endpoint, $data); if ($response && !is_wp_error($response)) { $ttl = is_int($ttl) ? $ttl : $this->ttl; $this->cache->set($key, $response, $ttl); } return $response; } public function postRequest(string $endpoint, array $data = []): array { return $this->makeRequest('POST', $endpoint, $data); } public function putRequest(string $endpoint, array $data = []): array { return $this->makeRequest('PUT', $endpoint, $data); } public function deleteRequest(string $endpoint, array $data = []): array { return $this->makeRequest('DELETE', $endpoint, $data); } protected function makeRequest(string $method, string $endpoint, array $data = []): array { if (is_null($this->baseUrl)) { error_log('Can not make request. No $baseUrl set for '.$this->service_name); return []; } if ($this->requestLimiter && !$this->awaitRateLimit($endpoint)) { $error = new \WP_Error('rate_limit_exceeded', "Rate limit exceeded for {$endpoint}"); return $this->handleResponse($error, $method, $endpoint); } $url = rtrim($this->baseUrl, '/') . '/' . ltrim($endpoint, '/'); $args = [ 'method' => $method, 'headers' => $this->getRequestHeaders(), 'timeout' => 15, ]; if (!empty($data)) { if (in_array($method, ['GET', 'DELETE'], true)) { $url = add_query_arg($data, $url); } else { $args['body'] = wp_json_encode($data); } } $response = wp_remote_request($url, $args); $this->requestLimiter?->recordRequest($endpoint); return $this->handleResponse($response, $method, $endpoint); } /** * Waits (with jitter) for the rate limit to clear, up to a few attempts. * Returns false if still limited after max attempts. */ protected function awaitRateLimit(string $endpoint, int $maxAttempts = 3): bool { $attempts = 0; while (!$this->requestLimiter->checkRateLimit($endpoint)) { $attempts++; if ($attempts >= $maxAttempts) { return false; } usleep(random_int(250_000, 750_000)); // 250-750ms jitter } return true; } }