Cold review caught a regression from the two previous commits. Instagram and
Threads use long-lived tokens refreshed by EXTENDING the access_token itself
(grant_type=ig_refresh_token / th_refresh_token) — they have no separate
refresh_token and CANNOT be refreshed once expired. The anti-over-rotation rule
("only refresh a token once it's actually expired") is right for rotating
single-use refresh_token platforms but wrong for these: it left IG/Threads
tokens to lapse, after which the extend call fails and the account disconnects
(~every 60 days).
Gate the anti-rotation on the platform's refresh model:
- Platform::extendsAccessTokenOnRefresh() — true for Instagram/Threads.
- SocialAccount::needsProactiveTokenRefresh() — expired for rotating platforms,
OR expiring-soon for extension platforms (restores isTokenExpiringSoon).
- RefreshSocialToken extends (refreshToken) extension-model tokens while still
valid, and verifies (access-token-first) rotating ones.
- All 23 publisher/analytics pre-checks now use needsProactiveTokenRefresh().
Tests: proactive job extends a still-valid Instagram token; a model test covers
the rotating-vs-extension branching; existing X/LinkedIn anti-rotation tests
are unchanged.
Refs #126
51 lines
1.7 KiB
PHP
51 lines
1.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Exceptions\PlatformUnavailableException;
|
|
use App\Exceptions\TokenExpiredException;
|
|
use App\Models\SocialAccount;
|
|
use App\Services\Social\ConnectionVerifier;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Throwable;
|
|
|
|
class RefreshSocialToken implements ShouldQueue
|
|
{
|
|
use Queueable;
|
|
|
|
public int $tries = 1;
|
|
|
|
public function __construct(public SocialAccount $account) {}
|
|
|
|
public function handle(ConnectionVerifier $verifier): void
|
|
{
|
|
try {
|
|
if ($this->account->platform->extendsAccessTokenOnRefresh()) {
|
|
// Instagram/Threads extend the long-lived token itself and
|
|
// can't be refreshed once expired, so extend it while it's
|
|
// still valid instead of waiting for it to fail.
|
|
$verifier->refreshToken($this->account);
|
|
} else {
|
|
$verifier->verify($this->account);
|
|
}
|
|
} catch (PlatformUnavailableException $e) {
|
|
Log::warning('Token refresh skipped: platform unavailable', [
|
|
'account_id' => $this->account->id,
|
|
'platform' => $this->account->platform->value,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
} catch (TokenExpiredException $e) {
|
|
$this->account->markAsTokenExpired($e->getMessage());
|
|
} catch (Throwable $e) {
|
|
Log::warning('Proactive token refresh failed', [
|
|
'account_id' => $this->account->id,
|
|
'platform' => $this->account->platform->value,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
}
|