diff --git a/resources/js/app.js b/resources/js/app.js index 9b5ef1a..fa8a6ae 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -202,13 +202,18 @@ document.addEventListener('alpine:init', () => { // Keep the active-server session in sync with the server-details page on screen, even // when wire:navigate restores a page from its bfcache (Back/Forward) without re-running // Show::mount(). The details page tags its root with data-clusev-activate=. +let clusevActivateAbort; document.addEventListener('livewire:navigated', () => { + // Abort any prior in-flight activation so only the LATEST navigation can win — a + // stale request must never overwrite a newer server selection. + clusevActivateAbort?.abort(); const el = document.querySelector('[data-clusev-activate]'); if (!el) return; + clusevActivateAbort = new AbortController(); const token = document.querySelector('meta[name="csrf-token"]')?.content; fetch(el.dataset.clusevActivate, { method: 'POST', headers: { 'X-CSRF-TOKEN': token ?? '', 'X-Requested-With': 'XMLHttpRequest' }, - keepalive: true, + signal: clusevActivateAbort.signal, }).catch(() => {}); }); diff --git a/tests/Feature/ServerContextSyncTest.php b/tests/Feature/ServerContextSyncTest.php index dfc6963..4d7c0ec 100644 --- a/tests/Feature/ServerContextSyncTest.php +++ b/tests/Feature/ServerContextSyncTest.php @@ -21,7 +21,8 @@ class ServerContextSyncTest extends TestCase public function test_activate_endpoint_sets_active_server(): void { - $this->actingAs(User::factory()->create()); + // onboarded user — the route sits behind EnsureSecurityOnboarded + $this->actingAs(User::factory()->create(['two_factor_secret' => 'x', 'two_factor_confirmed_at' => now()])); $b = $this->server('B'); $this->post(route('servers.activate', $b))->assertNoContent();