Skip to content

Commit a818634

Browse files
committed
fix(browser): reattach the intended page
1 parent c8c193c commit a818634

2 files changed

Lines changed: 32 additions & 8 deletions

File tree

packages/bcode-browser/src/cdp/session.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class Session implements Transport {
4747
private nextId = 1;
4848
private pending = new Map<number, Pending>();
4949
private activeSessionId: string | undefined;
50+
private activeTargetId: string | undefined;
5051
private reattachPromise?: Promise<void>;
5152
private enabledDomains = new Map<string, Map<string, unknown>>();
5253
private eventListeners: Array<(method: string, params: unknown, sessionId?: string) => void> = [];
@@ -135,6 +136,7 @@ export class Session implements Transport {
135136
const previous = this.ws;
136137
this.ws = ws;
137138
this.activeSessionId = undefined;
139+
this.activeTargetId = undefined;
138140
this.enabledDomains.clear();
139141
finish();
140142
if (previous && previous !== ws) {
@@ -148,6 +150,7 @@ export class Session implements Transport {
148150
if (this.ws === ws) {
149151
this.ws = undefined;
150152
this.activeSessionId = undefined;
153+
this.activeTargetId = undefined;
151154
this.enabledDomains.clear();
152155
}
153156
finish(new Error('WS closed before open (likely 403 or port closed)'));
@@ -170,12 +173,14 @@ export class Session implements Transport {
170173
async use(targetId: string): Promise<string> {
171174
const r = await this._call('Target.attachToTarget', { targetId, flatten: true }) as { sessionId: string };
172175
this.activeSessionId = r.sessionId;
176+
this.activeTargetId = targetId;
173177
return r.sessionId;
174178
}
175179

176180
/** Set the active sessionId directly (e.g. one you already attached). */
177181
setActiveSession(sessionId: string | undefined): void {
178182
this.activeSessionId = sessionId;
183+
this.activeTargetId = undefined;
179184
}
180185

181186
getActiveSession(): string | undefined {
@@ -228,6 +233,7 @@ export class Session implements Transport {
228233
async _call(method: string, params: unknown = {}): Promise<unknown> {
229234
const browserLevel = isBrowserLevel(method);
230235
const sentSessionId = browserLevel ? undefined : this.activeSessionId;
236+
const sentTargetId = browserLevel ? undefined : this.activeTargetId;
231237
try {
232238
return await this.send(method, params, sentSessionId);
233239
} catch (error) {
@@ -236,7 +242,9 @@ export class Session implements Transport {
236242
// Chrome explicitly rejected the command before executing it, so this is
237243
// safe to retry once. Socket drops are deliberately not retried: Chrome
238244
// may have applied a click or submission before the response was lost.
239-
if (this.activeSessionId === sentSessionId) await this.reattachFirstPage(sentSessionId);
245+
if (this.activeSessionId === sentSessionId) {
246+
await this.reattachPage(sentSessionId, sentTargetId);
247+
}
240248
else if (this.reattachPromise) await this.reattachPromise;
241249
if (!this.activeSessionId || this.activeSessionId === sentSessionId) throw error;
242250
return this.send(method, params, this.activeSessionId);
@@ -273,10 +281,10 @@ export class Session implements Transport {
273281
});
274282
}
275283

276-
private async reattachFirstPage(staleSessionId: string): Promise<void> {
284+
private async reattachPage(staleSessionId: string, staleTargetId?: string): Promise<void> {
277285
if (this.reattachPromise) return this.reattachPromise;
278286

279-
const attempt = this.attachFirstPage(staleSessionId);
287+
const attempt = this.attachPage(staleSessionId, staleTargetId);
280288
this.reattachPromise = attempt;
281289
try {
282290
await attempt;
@@ -285,11 +293,15 @@ export class Session implements Transport {
285293
}
286294
}
287295

288-
private async attachFirstPage(staleSessionId: string): Promise<void> {
296+
private async attachPage(staleSessionId: string, staleTargetId?: string): Promise<void> {
289297
const domainsToRestore = [...(this.enabledDomains.get(staleSessionId)?.entries() ?? [])];
290298
const { targetInfos } = await this.domains.Target.getTargets({});
291299
const pages = targetInfos as PageTarget[];
292-
const targetId = pages.find(isUsablePageTarget)?.targetId
300+
const exactTarget = staleTargetId
301+
? pages.find(target => target.type === 'page' && target.targetId === staleTargetId)
302+
: undefined;
303+
const targetId = exactTarget?.targetId
304+
?? (!staleTargetId ? pages.find(isUsablePageTarget)?.targetId : undefined)
293305
?? (await this.domains.Target.createTarget({ url: 'about:blank' })).targetId;
294306
const sessionId = await this.use(targetId);
295307
await Promise.all(

packages/bcode-browser/test/cdp-recovery.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test("a missing page session is reattached once and the rejected command is retr
1111
let getTargetsCount = 0
1212
let staleCommandCount = 0
1313
const commandSessions: string[] = []
14+
const attachedTargets: string[] = []
1415
const enabledDomains: string[] = []
1516
const server = Bun.serve({
1617
port: 0,
@@ -22,6 +23,7 @@ test("a missing page session is reattached once and the rejected command is retr
2223
const message = JSON.parse(String(raw))
2324
if (message.method === "Target.attachToTarget") {
2425
attachCount++
26+
attachedTargets.push(message.params.targetId)
2527
socket.send(JSON.stringify({ id: message.id, result: { sessionId: `session-${attachCount}` } }))
2628
return
2729
}
@@ -31,7 +33,10 @@ test("a missing page session is reattached once and the rejected command is retr
3133
socket.send(JSON.stringify({
3234
id: message.id,
3335
result: {
34-
targetInfos: [{ targetId: "page-1", title: "Page", type: "page", url: "https://example.com" }],
36+
targetInfos: [
37+
{ targetId: "other-page", title: "Other", type: "page", url: "https://other.example" },
38+
{ targetId: "page-1", title: "Page", type: "page", url: "https://example.com" },
39+
],
3540
},
3641
}))
3742
}, 10)
@@ -81,6 +86,7 @@ test("a missing page session is reattached once and the rejected command is retr
8186
expect(second.result.value).toBe("2")
8287
expect(third.result.value).toBe("3")
8388
expect(attachCount).toBe(2)
89+
expect(attachedTargets).toEqual(["page-1", "page-1"])
8490
expect(getTargetsCount).toBe(1)
8591
expect(enabledDomains).toEqual(["Debugger.enable", "Debugger.enable"])
8692
expect(commandSessions).toEqual([
@@ -229,9 +235,10 @@ test("reattach reuses an existing about:blank target", async () => {
229235
}
230236
})
231237

232-
test("reattach creates a blank page when only internal targets remain", async () => {
238+
test("reattach creates a blank page instead of switching to another live target", async () => {
233239
let attachCount = 0
234240
let createdTarget: unknown
241+
const attachedTargets: string[] = []
235242
const server = Bun.serve({
236243
port: 0,
237244
fetch(req, bunServer) {
@@ -242,14 +249,18 @@ test("reattach creates a blank page when only internal targets remain", async ()
242249
const message = JSON.parse(String(raw))
243250
if (message.method === "Target.attachToTarget") {
244251
attachCount++
252+
attachedTargets.push(message.params.targetId)
245253
socket.send(JSON.stringify({ id: message.id, result: { sessionId: `session-${attachCount}` } }))
246254
return
247255
}
248256
if (message.method === "Target.getTargets") {
249257
socket.send(JSON.stringify({
250258
id: message.id,
251259
result: {
252-
targetInfos: [{ targetId: "settings", title: "Settings", type: "page", url: "chrome://settings" }],
260+
targetInfos: [
261+
{ targetId: "other-page", title: "Other", type: "page", url: "https://other.example" },
262+
{ targetId: "settings", title: "Settings", type: "page", url: "chrome://settings" },
263+
],
253264
},
254265
}))
255266
return
@@ -287,6 +298,7 @@ test("reattach creates a blank page when only internal targets remain", async ()
287298
expect(result.result.value).toBe(true)
288299
expect(createdTarget).toEqual({ url: "about:blank" })
289300
expect(attachCount).toBe(2)
301+
expect(attachedTargets).toEqual(["old-page", "fresh-page"])
290302
} finally {
291303
session.close()
292304
server.stop(true)

0 commit comments

Comments
 (0)