diff --git a/app/Jobs/RefreshSocialToken.php b/app/Jobs/RefreshSocialToken.php index 86957f2d..25b6b1bf 100644 --- a/app/Jobs/RefreshSocialToken.php +++ b/app/Jobs/RefreshSocialToken.php @@ -26,20 +26,12 @@ public function handle(ConnectionVerifier $verifier): void try { $verifier->refreshToken($this->account); } catch (PlatformUnavailableException $e) { - // Platform is down (5xx / network). Leave the account alone — - // next scheduled run will try again. Critically, do NOT mark - // the account expired: that would trigger a false-positive - // "reconnect your account" notification. Log::warning('Token refresh skipped: platform unavailable', [ 'account_id' => $this->account->id, 'platform' => $this->account->platform->value, 'error' => $e->getMessage(), ]); } catch (TokenExpiredException $e) { - // refresh_token rejected by the provider (revoked / rotated / - // expired beyond refresh). Mark the account so the user is - // notified immediately instead of waiting for the next failed - // publish or the daily verify pass. $this->account->markAsTokenExpired($e->getMessage()); } catch (Throwable $e) { Log::warning('Proactive token refresh failed', [ diff --git a/app/Jobs/VerifyWorkspaceConnections.php b/app/Jobs/VerifyWorkspaceConnections.php index 5174b13d..6d01c957 100644 --- a/app/Jobs/VerifyWorkspaceConnections.php +++ b/app/Jobs/VerifyWorkspaceConnections.php @@ -66,8 +66,6 @@ private function verifyAccount(ConnectionVerifier $verifier, SocialAccount $acco return true; } catch (PlatformUnavailableException $e) { - // Platform is down (5xx / network). The account's token is not - // provably bad — skip silently and try again next pass. Log::warning('Social account verification skipped: platform unavailable', [ 'account_id' => $account->id, 'platform' => $account->platform->value, diff --git a/app/Services/Social/ConnectionVerifier.php b/app/Services/Social/ConnectionVerifier.php index e5c9fc17..d13bb620 100644 --- a/app/Services/Social/ConnectionVerifier.php +++ b/app/Services/Social/ConnectionVerifier.php @@ -28,9 +28,6 @@ public function verify(SocialAccount $account): bool // LinkedIn, etc.) invalidate the previous refresh_token on each // refresh, so proactive refreshes during races cause false-positive // disconnects even though the access_token still works fine. - // - // PlatformUnavailableException from refresh propagates naturally so - // the caller can distinguish "platform is down" from "token bad". if ($account->is_token_expired) { $this->refreshToken($account); @@ -76,9 +73,10 @@ private function callVerifyEndpoint(SocialAccount $account): bool * Refresh the account's token via the platform-specific OAuth flow. * Callers that want the smart "try access_token first" behavior should * use verify() instead. This method always attempts a refresh under - * the per-account lock and throws TokenExpiredException on failure. + * the per-account lock. * - * @throws TokenExpiredException if refresh fails + * @throws TokenExpiredException if refresh is rejected by the provider (4xx) + * @throws PlatformUnavailableException if the platform is unreachable (5xx / network) */ public function refreshToken(SocialAccount $account): void { @@ -168,9 +166,6 @@ private function refreshBlueskyToken(SocialAccount $account): void $service = $account->meta['service'] ?? config('trypost.platforms.bluesky.default_service'); $client = TokenRefreshClient::for(Platform::Bluesky); - // Try refresh token first. Connection errors / 5xx surface as - // PlatformUnavailableException (Bluesky is down — don't touch the - // account's status). 4xx falls through to the re-auth fallback. try { $response = $client->send(fn () => Http::withToken($account->refresh_token) ->post("{$service}/xrpc/com.atproto.server.refreshSession")); @@ -512,7 +507,7 @@ private function verifyBluesky(SocialAccount $account): bool private function verifyMastodon(SocialAccount $account): bool { - $instance = $account->meta['instance'] ?? 'https://mastodon.social'; + $instance = $account->meta['instance'] ?? config('trypost.platforms.mastodon.default_instance'); $response = Http::withToken($account->access_token) ->get("{$instance}/api/v1/accounts/verify_credentials"); diff --git a/config/trypost.php b/config/trypost.php index 29fcfb95..45f1d93d 100644 --- a/config/trypost.php +++ b/config/trypost.php @@ -116,6 +116,8 @@ ], 'mastodon' => [ 'enabled' => env('MASTODON_ENABLED', true), + // Default instance used when the account has no `meta.instance` override. + 'default_instance' => env('MASTODON_DEFAULT_INSTANCE', 'https://mastodon.social'), ], ], diff --git a/tests/Feature/Jobs/RefreshSocialTokenTest.php b/tests/Feature/Jobs/RefreshSocialTokenTest.php index 172076a7..c9a9226d 100644 --- a/tests/Feature/Jobs/RefreshSocialTokenTest.php +++ b/tests/Feature/Jobs/RefreshSocialTokenTest.php @@ -86,7 +86,6 @@ (new RefreshSocialToken($this->account))->handle($verifier); - // Critically: account status stays Connected, no notification dispatched. expect($this->account->fresh()->status)->toBe(Status::Connected); Queue::assertNotPushed(SendNotification::class); }); diff --git a/tests/Feature/Services/Social/ConnectionVerifierTest.php b/tests/Feature/Services/Social/ConnectionVerifierTest.php index 9d025385..8488a972 100644 --- a/tests/Feature/Services/Social/ConnectionVerifierTest.php +++ b/tests/Feature/Services/Social/ConnectionVerifierTest.php @@ -411,7 +411,6 @@ test('bluesky 5xx during refresh raises PlatformUnavailable even when password fallback is stored', function () { Http::fake([ - // Both endpoints return 5xx — the platform is genuinely down. 'bsky.social/xrpc/com.atproto.server.refreshSession' => Http::response('upstream timeout', 503), 'bsky.social/xrpc/com.atproto.server.createSession' => Http::response('upstream timeout', 503), ]); diff --git a/tests/Feature/VerifyWorkspaceConnectionsTest.php b/tests/Feature/VerifyWorkspaceConnectionsTest.php index 5eaff679..19976701 100644 --- a/tests/Feature/VerifyWorkspaceConnectionsTest.php +++ b/tests/Feature/VerifyWorkspaceConnectionsTest.php @@ -137,7 +137,6 @@ VerifyWorkspaceConnections::dispatch($workspace); - // Status untouched, no notification email. expect($account->fresh()->status)->toBe(Status::Connected); Mail::assertNothingQueued(); });