MCPcopy
hub / github.com/garrytan/gstack / restoreState

Method restoreState

browse/src/browser-manager.ts:1307–1389  ·  view source on GitHub ↗

* Restore browser state into the current context: cookies, pages, storage. * Navigates to saved URLs, restores storage, wires page events. * Failures on individual pages are swallowed — partial restore is better than none.

(state: BrowserState)

Source from the content-addressed store, hash-verified

1305 * Failures on individual pages are swallowed — partial restore is better than none.
1306 */
1307 async restoreState(state: BrowserState): Promise<void> {
1308 if (!this.context) throw new Error('Browser not launched');
1309
1310 // Restore cookies
1311 if (state.cookies.length > 0) {
1312 await this.context.addCookies(state.cookies);
1313 }
1314
1315 // Clear stale ownership — the old tab IDs are gone. We'll re-add per-tab
1316 // owners below as each saved tab gets a fresh ID. Without this reset, old
1317 // tabId → clientId entries would linger and match new tabs with the same
1318 // sequential IDs, silently granting ownership to the wrong clients.
1319 this.tabOwnership.clear();
1320
1321 // Re-create pages
1322 let activeId: number | null = null;
1323 for (const saved of state.pages) {
1324 const page = await this.context.newPage();
1325 const id = this.nextTabId++;
1326 this.pages.set(id, page);
1327 const newSession = new TabSession(page);
1328 this.tabSessions.set(id, newSession);
1329 this.wirePageEvents(page);
1330
1331 // Restore tab ownership for the new ID — preserves scoped-agent isolation
1332 // across context recreation (viewport --scale, user-agent change, handoff).
1333 if (saved.owner) {
1334 this.tabOwnership.set(id, saved.owner);
1335 }
1336
1337 if (saved.loadedHtml) {
1338 // Replay load-html content via setTabContent — this rehydrates
1339 // TabSession.loadedHtml so the next saveState sees it. page.setContent()
1340 // alone would restore the DOM but lose the replay metadata.
1341 try {
1342 await newSession.setTabContent(saved.loadedHtml, { waitUntil: saved.loadedHtmlWaitUntil });
1343 } catch (err: any) {
1344 console.warn(`[browse] Failed to replay loadedHtml for tab ${id}: ${err.message}`);
1345 }
1346 } else if (saved.url) {
1347 // Validate the saved URL before navigating — the state file is user-writable and
1348 // a tampered URL could navigate to cloud metadata endpoints. Use the normalized
1349 // return value so file:// forms get consistent treatment with live goto.
1350 let normalizedUrl: string;
1351 try {
1352 normalizedUrl = await validateNavigationUrl(saved.url);
1353 } catch (err: any) {
1354 console.warn(`[browse] Skipping invalid URL in state file: ${saved.url} — ${err.message}`);
1355 continue;
1356 }
1357 await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 }).catch(() => {});
1358 }
1359
1360 if (saved.storage) {
1361 try {
1362 await page.evaluate((s: { localStorage: Record<string, string>; sessionStorage: Record<string, string> }) => {
1363 if (s.localStorage) {
1364 for (const [k, v] of Object.entries(s.localStorage)) {

Callers 3

recreateContextMethod · 0.95
handoffMethod · 0.95
handleMetaCommandFunction · 0.80

Calls 8

wirePageEventsMethod · 0.95
setTabContentMethod · 0.95
newTabMethod · 0.95
clearRefsMethod · 0.95
validateNavigationUrlFunction · 0.90
setMethod · 0.80
clearMethod · 0.45
gotoMethod · 0.45

Tested by

no test coverage detected