| | |
| | | protected array $apiEndpoints = []; // Valid endpoint paths for this service |
| | | protected string $apiVersion = ''; // API version string (e.g., 'v2', '2024-01-01') |
| | | |
| | | protected int $refresh_interval = 0; //seconds before expiry to refresh tokens. 0 to disable |
| | | |
| | | /** |
| | | * OAuth Configuration |
| | |
| | | |
| | | // Retry with backoff for server errors |
| | | if ($attempt < $this->maxRetries && !$this->isClientError($e)) { |
| | | sleep($this->retryDelays[$attempt - 1] ?? 5); |
| | | $delay = pow(2, $attempt) * 1000000; // 2^attempt seconds in microseconds |
| | | $jitter = rand(0, $delay * 0.3); // Add 0-30% jitter |
| | | usleep($delay + $jitter); |
| | | } else { |
| | | break; |
| | | } |
| | |
| | | } |
| | | |
| | | if (!empty($this->credentials)) { |
| | | if ($this->isOAuthService && $this->hasOAuthCredentials() && !$this->isOAuthValid()) { |
| | | $this->logDebug('OAuth token expired, attempting refresh'); |
| | | if (!$this->refreshOAuthToken()) { |
| | | $this->logError('Failed to refresh OAuth token'); |
| | | if ($this->isOAuthService && $this->hasOAuthCredentials()) { |
| | | // Check if token is expired first |
| | | if (!$this->isOAuthValid()) { |
| | | $this->logDebug('OAuth token expired, attempting refresh'); |
| | | if (!$this->refreshOAuthToken()) { |
| | | $this->logError('Failed to refresh expired OAuth token'); |
| | | } |
| | | } |
| | | // Check if we should proactively refresh (before expiry) |
| | | elseif ($this->shouldRefreshToken()) { |
| | | $this->logDebug('OAuth token should be refreshed proactively'); |
| | | if (!$this->refreshOAuthToken()) { |
| | | $this->logError('Failed to proactively refresh OAuth token'); |
| | | // Not critical - token is still valid |
| | | } |
| | | } |
| | | } |
| | | $this->initialize(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Check if token should be proactively refreshed |
| | | * Different from isOAuthValid() which checks if token is actually expired |
| | | */ |
| | | protected function shouldRefreshToken(): bool |
| | | { |
| | | if (!$this->isOAuthService || $this->refresh_interval === 0) { |
| | | return false; |
| | | } |
| | | |
| | | // If no expiry info, we can't proactively refresh |
| | | if (empty($this->credentials['expires_at'])) { |
| | | return false; |
| | | } |
| | | |
| | | $expires_at = intval($this->credentials['expires_at']); |
| | | $time_until_expiry = $expires_at - time(); |
| | | |
| | | // Refresh if we're within the refresh interval window |
| | | return $time_until_expiry > 0 && $time_until_expiry <= $this->refresh_interval; |
| | | } |
| | | /** |
| | | * Get time until token refresh is recommended |
| | | * Useful for displaying in admin UI |
| | | */ |
| | | public function getTimeUntilRefresh(): ?int |
| | | { |
| | | if ($this->refresh_interval === 0 || empty($this->credentials['expires_at'])) { |
| | | return null; |
| | | } |
| | | |
| | | $expires_at = intval($this->credentials['expires_at']); |
| | | $refresh_at = $expires_at - $this->refresh_interval; |
| | | $time_until_refresh = $refresh_at - time(); |
| | | |
| | | return max(0, $time_until_refresh); |
| | | } |
| | | |
| | | /** |
| | | * Get token freshness status |
| | | * Returns: 'fresh', 'should_refresh', 'expired', or 'no_expiry_info' |
| | | */ |
| | | public function getTokenStatus(): string |
| | | { |
| | | if (!$this->isOAuthService) { |
| | | return 'not_oauth'; |
| | | } |
| | | |
| | | if (empty($this->credentials['access_token'])) { |
| | | return 'no_token'; |
| | | } |
| | | |
| | | if (empty($this->credentials['expires_at'])) { |
| | | return 'no_expiry_info'; |
| | | } |
| | | |
| | | $expires_at = intval($this->credentials['expires_at']); |
| | | $now = time(); |
| | | |
| | | if ($expires_at <= $now) { |
| | | return 'expired'; |
| | | } |
| | | |
| | | if ($this->shouldRefreshToken()) { |
| | | return 'should_refresh'; |
| | | } |
| | | |
| | | return 'fresh'; |
| | | } |
| | | /** |
| | | * Refresh OAuth token |
| | | */ |
| | | protected function refreshOAuthToken(): bool |