Jake Vanderwerf
2025-10-20 e729f920139f0c65902be2d6b2c32466b08375e8
inc/integrations/Integrations.php
@@ -35,6 +35,7 @@
   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
@@ -806,7 +807,9 @@
            // 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;
            }
@@ -1341,10 +1344,21 @@
      }
      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();
@@ -1786,6 +1800,75 @@
   }
   /**
    * 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