Skip to content

Commit 68518b8

Browse files
committed
[chrome] implement proper saving of browser state and cookies
1 parent c3db14c commit 68518b8

File tree

4 files changed

+53
-131
lines changed

4 files changed

+53
-131
lines changed

packages/chrome/src/Browser.tsx

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
showDownloadsPopup,
1515
} from "./components/Omnibar/Omnibar";
1616
import type { RawDownload } from "./proxy/fetch";
17+
import { CookieJar } from "@mercuryworkshop/scramjet/bundled";
18+
import { getSerializedBrowserState, markDirty } from "./storage";
1719
export const pushTab = createDelegate<Tab>();
1820
export const popTab = createDelegate<Tab>();
1921
export const forceScreenshot = createDelegate<Tab>();
@@ -28,6 +30,7 @@ export type SerializedBrowser = {
2830
activetab: number;
2931
bookmarks: BookmarkEntry[];
3032
settings: Settings;
33+
cookiedump: string;
3134
};
3235

3336
export type GlobalHistoryEntry = {
@@ -85,6 +88,8 @@ export class Browser extends StatefulClass {
8588

8689
unfocusframes: boolean = false;
8790

91+
cookieJar: CookieJar = new CookieJar();
92+
8893
downloadProgress = 0;
8994

9095
settings: Stateful<Settings> = createState({
@@ -103,8 +108,6 @@ export class Browser extends StatefulClass {
103108
constructor() {
104109
super(createState(Object.create(Browser.prototype)));
105110

106-
setInterval(saveBrowserState, 10000);
107-
108111
// scramjet.addEventListener("download", (e) => {
109112
// this.startDownload(e.download);
110113
// });
@@ -203,6 +206,7 @@ export class Browser extends StatefulClass {
203206
bookmarks: this.bookmarks,
204207
settings: { ...this.settings },
205208
globalDownloadHistory: this.globalDownloadHistory,
209+
cookiedump: this.cookieJar.dump(),
206210
};
207211
}
208212
deserialize(de: SerializedBrowser) {
@@ -227,6 +231,7 @@ export class Browser extends StatefulClass {
227231
this.bookmarks = de.bookmarks.map(createState);
228232
this.globalDownloadHistory = de.globalDownloadHistory.map(createState);
229233
this.settings = createState(de.settings);
234+
this.cookieJar.load(de.cookiedump);
230235
// this.activetab = this.tabs.find((t) => t.id == de.activetab)!;
231236
}
232237

@@ -286,44 +291,12 @@ export class Browser extends StatefulClass {
286291
}
287292
}
288293

289-
let loaded = false;
290-
export async function saveBrowserState() {
291-
if (!loaded) return;
292-
293-
let ser = browser.serialize();
294-
295-
if (import.meta.env.VITE_PUTER_BRANDING) {
296-
await puter.kv.set("browserstate", JSON.stringify(ser));
297-
} else {
298-
localStorage["browserstate"] = JSON.stringify(ser);
299-
}
300-
301-
// if (!import.meta.env.VITE_LOCAL) {
302-
// let data = await serializeAll();
303-
// await puter.kv.set("browserdata", JSON.stringify(data));
304-
// }
305-
}
294+
export let browserLoaded = false;
306295

307296
export async function initBrowser() {
308297
browser = new Browser();
309298

310-
// if (!import.meta.env.VITE_LOCAL) {
311-
// let de = await puter.kv.get("browserdata");
312-
// if (de) {
313-
// try {
314-
// await deserializeAll(JSON.parse(de));
315-
// } catch (e) {
316-
// console.error("Error while loading browser data:", e);
317-
// }
318-
// }
319-
// }
320-
321-
let de;
322-
if (import.meta.env.VITE_PUTER_BRANDING) {
323-
de = await puter.kv.get("browserstate");
324-
} else {
325-
de = localStorage["browserstate"];
326-
}
299+
let de = await getSerializedBrowserState();
327300
if (de) {
328301
try {
329302
browser.deserialize(JSON.parse(de));
@@ -334,12 +307,13 @@ export async function initBrowser() {
334307
browser = new Browser();
335308
let tab = browser.newTab();
336309
browser.activetab = tab;
310+
markDirty();
337311
}
338312
} else {
339313
let tab = browser.newTab();
340314
browser.activetab = tab;
341315
}
342316

343317
(self as any).browser = browser;
344-
loaded = true;
318+
browserLoaded = true;
345319
}

packages/chrome/src/main.tsx

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ LoadInterstitial.style = css`
7878
}
7979
`;
8080

81-
let swReadyResolve: () => void;
82-
export const serviceWorkerReady = new Promise<void>(
83-
(resolve) => (swReadyResolve = resolve)
84-
);
85-
8681
export async function mount(): Promise<HTMLElement> {
8782
try {
8883
let shell = <Shell></Shell>;
@@ -116,88 +111,3 @@ export async function mount(): Promise<HTMLElement> {
116111
throw e;
117112
}
118113
}
119-
120-
async function waitForControllerOrReady(timeoutMs = 10000): Promise<void> {
121-
if (navigator.serviceWorker.controller) return;
122-
123-
const ready = navigator.serviceWorker.ready.then(() => {});
124-
const controllerChanged = new Promise<void>((resolve) => {
125-
const onChange = () => {
126-
navigator.serviceWorker.removeEventListener("controllerchange", onChange);
127-
resolve();
128-
};
129-
navigator.serviceWorker.addEventListener("controllerchange", onChange, {
130-
once: true,
131-
} as any);
132-
});
133-
const timeout = new Promise<void>((resolve) =>
134-
setTimeout(resolve, timeoutMs)
135-
);
136-
137-
// Wait for whichever happens first; on timeout we continue to avoid blocking the UI.
138-
await Promise.race([ready, controllerChanged, timeout]);
139-
}
140-
141-
// mount();
142-
143-
// init();
144-
async function init() {
145-
const signin: any = <LoadInterstitial status={"Loading"}></LoadInterstitial>;
146-
document.body.append(signin);
147-
signin.showModal();
148-
149-
try {
150-
const registration = await navigator.serviceWorker.register("./sw.js");
151-
152-
// If already controlled or active, don't block the UI.
153-
if (navigator.serviceWorker.controller || registration.active) {
154-
signin.$.state.status = "Service worker active";
155-
signin.close();
156-
return;
157-
}
158-
159-
// Non-blocking progress updates on state transitions.
160-
const updateStatus = (sw: ServiceWorker | null) => {
161-
if (!sw) return;
162-
const set = (msg: string) => (signin.$.state.status = msg);
163-
const apply = () => {
164-
switch (sw.state) {
165-
case "installing":
166-
set("Installing service worker...");
167-
break;
168-
case "installed":
169-
set("Service worker installed, waiting to activate...");
170-
break;
171-
case "activating":
172-
set("Activating service worker...");
173-
break;
174-
case "activated":
175-
set("Service worker activated");
176-
break;
177-
case "redundant":
178-
set("Service worker became redundant");
179-
break;
180-
}
181-
};
182-
apply();
183-
sw.addEventListener("statechange", apply);
184-
};
185-
186-
updateStatus(registration.installing ?? registration.waiting ?? null);
187-
188-
// Wait for control or readiness with a timeout; don't hang the UI on updates.
189-
signin.$.state.status = "Waiting for service worker to take control...";
190-
await waitForControllerOrReady(10000);
191-
signin.$.state.status = "Service worker ready";
192-
signin.close();
193-
swReadyResolve();
194-
} catch (e) {
195-
console.error("Error during service worker registration:", e);
196-
// Always close the modal on error to prevent hanging UI.
197-
try {
198-
signin.close();
199-
} catch {}
200-
app.innerText =
201-
"Failed to register service worker. Check console for details.";
202-
}
203-
}

packages/chrome/src/proxy/scramjet.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ function findSelfSequence(
5757
}
5858

5959
export function createFetchHandler(controller: Controller) {
60-
const cookieJar = new CookieJar();
61-
6260
const getInjectScripts: ScramjetInterface["getInjectScripts"] = (
6361
meta,
6462
handler,
@@ -68,7 +66,7 @@ export function createFetchHandler(controller: Controller) {
6866
$injectLoad({
6967
sequence: ${JSON.stringify(findSelfSequence(self)!)},
7068
config: ${JSON.stringify(makeConfig())},
71-
cookies: ${JSON.stringify(cookieJar.dump())},
69+
cookies: ${JSON.stringify(browser.cookieJar.dump())},
7270
wisp: ${JSON.stringify(wispUrl)},
7371
codecEncode: ${codecEncode.toString()},
7472
codecDecode: ${codecDecode.toString()},
@@ -122,7 +120,7 @@ export function createFetchHandler(controller: Controller) {
122120
codecEncode,
123121
codecDecode,
124122
},
125-
cookieJar,
123+
cookieJar: browser.cookieJar,
126124
config: makeConfig(),
127125
prefix: controller.prefix,
128126
},

packages/chrome/src/storage.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { browser, browserLoaded } from "./Browser";
2+
3+
let dirty = false;
4+
export function markDirty() {
5+
dirty = true;
6+
}
7+
8+
async function saveBrowserState() {
9+
if (!browserLoaded) return;
10+
11+
let ser = browser.serialize();
12+
13+
if (import.meta.env.VITE_PUTER_BRANDING) {
14+
await puter.kv.set("browserstate", JSON.stringify(ser));
15+
} else {
16+
localStorage["browserstate"] = JSON.stringify(ser);
17+
}
18+
}
19+
20+
export async function getSerializedBrowserState(): Promise<string | null> {
21+
if (import.meta.env.VITE_PUTER_BRANDING) {
22+
return await puter.kv.get("browserstate");
23+
} else {
24+
return localStorage["browserstate"];
25+
}
26+
}
27+
28+
setInterval(() => {
29+
if (dirty) {
30+
saveBrowserState();
31+
dirty = false;
32+
}
33+
}, 5000);
34+
35+
window.addEventListener("beforeunload", (e) => {
36+
if (dirty) {
37+
e.preventDefault();
38+
e.returnValue = "";
39+
}
40+
});

0 commit comments

Comments
 (0)