mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-05 17:27:42 +02:00
[eric] browser: keep recent browsers alive across dashboard switches so sessions survive
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
// Tracks which browser card the user last interacted with (clicked into its page or its chrome),
|
||||
// so global shortcuts (Ctrl+R reload, Ctrl +/- zoom, Ctrl+Tab) target THAT browser instead of a
|
||||
// guess. Module-level and imperative on purpose: shortcut handlers read it on keydown, so no React
|
||||
// re-render is needed. Cleared the moment the user clicks anything that isn't a browser card.
|
||||
// The browser card you last clicked into, so global shortcuts (Ctrl+R, zoom, Ctrl+Tab) target it; imperative + read on keydown so no re-render, and cleared the moment you click off any browser card.
|
||||
let lastInteractedBrowserId: string | null = null;
|
||||
|
||||
// Recently-used browser ids, newest first; the top KEEP_ALIVE_CAP stay mounted across dashboard switches + off-screen so their sessionStorage (logins like Discord) survives, the rest get reclaimed by the normal suspend (LRU).
|
||||
const KEEP_ALIVE_CAP = 4;
|
||||
let recentBrowserIds: string[] = [];
|
||||
|
||||
export function setLastInteractedBrowser(browserId: string): void {
|
||||
lastInteractedBrowserId = browserId;
|
||||
recentBrowserIds = [browserId, ...recentBrowserIds.filter((id) => id !== browserId)].slice(0, 32);
|
||||
}
|
||||
|
||||
export function clearLastInteractedBrowser(): void {
|
||||
@@ -15,3 +17,17 @@ export function clearLastInteractedBrowser(): void {
|
||||
export function getLastInteractedBrowser(): string | null {
|
||||
return lastInteractedBrowserId;
|
||||
}
|
||||
|
||||
export function getKeepAliveBrowserIds(): string[] {
|
||||
return recentBrowserIds.slice(0, KEEP_ALIVE_CAP);
|
||||
}
|
||||
|
||||
export function isKeepAliveBrowser(browserId: string): boolean {
|
||||
return getKeepAliveBrowserIds().includes(browserId);
|
||||
}
|
||||
|
||||
// Drop a closed browser from focus + keep-alive tracking so a dead id can't hog a slot.
|
||||
export function forgetBrowser(browserId: string): void {
|
||||
recentBrowserIds = recentBrowserIds.filter((id) => id !== browserId);
|
||||
if (lastInteractedBrowserId === browserId) lastInteractedBrowserId = null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Dispatch } from '@reduxjs/toolkit';
|
||||
import { removeBrowserCard } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { getBrowserWebviews } from '@/shared/browserRegistry';
|
||||
import { forgetBrowser } from '@/shared/browserFocus';
|
||||
|
||||
interface CdpBridge {
|
||||
cdpDetachClean?: (wcId: number) => Promise<unknown>;
|
||||
@@ -34,5 +35,6 @@ export async function removeBrowserCardCleanly(
|
||||
dispatch: Dispatch,
|
||||
): Promise<void> {
|
||||
await detachBrowserCdp(browserId);
|
||||
forgetBrowser(browserId);
|
||||
dispatch(removeBrowserCard(browserId));
|
||||
}
|
||||
|
||||
@@ -1296,10 +1296,18 @@ const dashboardLayoutSlice = createSlice({
|
||||
delete state.glowingAgentCards[action.payload];
|
||||
},
|
||||
|
||||
resetLayout(state) {
|
||||
resetLayout(state, action: PayloadAction<{ keepBrowserIds?: string[] } | undefined>) {
|
||||
// Keep the recently-used (keep-alive) browser cards mounted across a dashboard switch so their webContents + sessionStorage survive (logged-in sites stay logged in); everything else is wiped for the fresh load. Their suspend entry rides along so a parked one isn't silently dropped.
|
||||
const keep = new Set(action.payload?.keepBrowserIds || []);
|
||||
const keptBrowsers: typeof state.browserCards = {};
|
||||
const keptSuspended: typeof state.suspendedBrowserCards = {};
|
||||
for (const id of keep) {
|
||||
if (state.browserCards[id]) keptBrowsers[id] = state.browserCards[id];
|
||||
if (state.suspendedBrowserCards[id]) keptSuspended[id] = state.suspendedBrowserCards[id];
|
||||
}
|
||||
state.cards = {};
|
||||
state.viewCards = {};
|
||||
state.browserCards = {};
|
||||
state.browserCards = keptBrowsers;
|
||||
state.workflowCards = {};
|
||||
state.workflowsHub = null;
|
||||
state.notes = {};
|
||||
@@ -1310,7 +1318,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.nextZOrder = 1;
|
||||
state.initialized = false;
|
||||
state.pendingFocusNoteId = null;
|
||||
state.suspendedBrowserCards = {};
|
||||
state.suspendedBrowserCards = keptSuspended;
|
||||
state.endingBrowserCards = {};
|
||||
state.pendingFocusWorkflowId = null;
|
||||
},
|
||||
@@ -1330,18 +1338,20 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (!isReconnectRefetch) {
|
||||
state.cards = action.payload.cards;
|
||||
state.viewCards = action.payload.viewCards;
|
||||
state.browserCards = action.payload.browserCards;
|
||||
for (const card of Object.values(state.browserCards)) {
|
||||
// Merge, don't replace: the keep-alive browser cards resetLayout preserved are ALREADY in state.browserCards with their webContents live. Keep them and add this dashboard's saved cards on top; on overlap (switching back to their own dashboard) the live data wins so the mounted webview isn't disturbed.
|
||||
const keptAlive = state.browserCards;
|
||||
const incoming = action.payload.browserCards;
|
||||
for (const card of Object.values(incoming)) {
|
||||
card.dashboard_id = ownerDashboardId;
|
||||
}
|
||||
// New cards boot parked (no guest process, title placeholder); the suspend hook wakes viewport-sized and agent-driven ones on its first pass. NEVER re-park a live keep-alive card, that snapshot-swap would kill its session.
|
||||
for (const id of Object.keys(incoming)) {
|
||||
if (keptAlive[id] === undefined) state.suspendedBrowserCards[id] = { dataUrl: '', capturedAt: 0 };
|
||||
}
|
||||
state.browserCards = { ...incoming, ...keptAlive };
|
||||
state.workflowCards = action.payload.workflowCards || {};
|
||||
state.workflowsHub = action.payload.workflowsHub || null;
|
||||
state.notes = action.payload.notes || {};
|
||||
// Cards boot parked (no guest process, title placeholder); the suspend hook wakes viewport-sized and agent-driven ones on its first pass. Beats mounting 100 webviews just to suspend 92 of them.
|
||||
state.suspendedBrowserCards = {};
|
||||
for (const id of Object.keys(action.payload.browserCards)) {
|
||||
state.suspendedBrowserCards[id] = { dataUrl: '', capturedAt: 0 };
|
||||
}
|
||||
} else {
|
||||
const occupied = collectOccupiedRects(state, action.payload.expandedSessionIds);
|
||||
addMissingCards(state.cards, action.payload.cards, occupied);
|
||||
|
||||
Reference in New Issue
Block a user