@broberg/lens-daemon 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/browser.ts +2 -1
- package/src/capture-session.ts +2 -1
- package/src/profiles.ts +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broberg/lens-daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "The Lens module of the cardmem daemon (flows, critic, coverage, capture sessions, the /lens/* routes and the cardmem-lens MCP) as a package. The daemon mounts createLensRouter(deps) + createLensMcpRouter(deps). Bun runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/browser.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// auth (F074.4) and concurrent captures never bleed into each other. The
|
|
11
11
|
// context + page are always closed in a finally, even on capture error.
|
|
12
12
|
|
|
13
|
-
import { bindProfileChannel, claimProfile, profileDir, type ProfileChannel } from './profiles.ts';
|
|
13
|
+
import { bindProfileChannel, claimProfile, killOwnBrowsersOn, profileDir, type ProfileChannel } from './profiles.ts';
|
|
14
14
|
import {
|
|
15
15
|
chromium,
|
|
16
16
|
webkit,
|
|
@@ -444,6 +444,7 @@ async function launchRunBrowser(opts: WithPageOptions, b: RunBrowser): Promise<{
|
|
|
444
444
|
});
|
|
445
445
|
return { context, userDataDir };
|
|
446
446
|
} catch (err) {
|
|
447
|
+
killOwnBrowsersOn(userDataDir);
|
|
447
448
|
if (!b.profile) await rm(userDataDir, { recursive: true, force: true }).catch(() => {});
|
|
448
449
|
const msg = err instanceof Error ? err.message.split('\n')[0]! : String(err);
|
|
449
450
|
throw new Error(`browser "${b.channel}" did not start: ${msg}${b.channel === 'chrome' ? ' — is Google Chrome installed on the daemon host?' : ''}`);
|
package/src/capture-session.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// are confined to ~/.lens/state so a bad state_path can't clobber an arbitrary
|
|
19
19
|
// file. The endpoint is loopback-only (127.0.0.1) behind the daemon's auth.
|
|
20
20
|
|
|
21
|
-
import { bindProfileChannel, claimProfile, profileDir, type ProfileChannel } from './profiles.ts';
|
|
21
|
+
import { bindProfileChannel, claimProfile, killOwnBrowsersOn, profileDir, type ProfileChannel } from './profiles.ts';
|
|
22
22
|
import { EXTENSION_CHANNEL } from './browser.ts';
|
|
23
23
|
import { chromium, type Browser, type BrowserContext, type Page } from 'playwright';
|
|
24
24
|
import { z } from 'zod';
|
|
@@ -418,6 +418,7 @@ export async function startCaptureSession(input: {
|
|
|
418
418
|
try {
|
|
419
419
|
context = await chromium.launchPersistentContext(dir, profileLaunchOptions(channel));
|
|
420
420
|
} catch (err) {
|
|
421
|
+
killOwnBrowsersOn(dir);
|
|
421
422
|
releaseProfile();
|
|
422
423
|
throw err;
|
|
423
424
|
}
|
package/src/profiles.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// F009.132 — a fixed browser profile per site. Google binds its session to the
|
|
2
2
|
// browser that holds it, so a login only survives if it happens in, and is reused
|
|
3
3
|
// from, ONE profile of ONE browser build. The profile lives on the daemon host.
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
4
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
6
|
import { homedir } from 'node:os';
|
|
6
7
|
import { join } from 'node:path';
|
|
@@ -52,3 +53,31 @@ export function bindProfileChannel(name: string, channel: ProfileChannel): void
|
|
|
52
53
|
);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
// F009.136 — when a launch on a profile fails, Playwright can leave the Chrome it
|
|
58
|
+
// started running (measured twice on 25 Oct: a headless Chrome stayed up holding the
|
|
59
|
+
// profile, so every retry hung on the lock it left). Kill only processes this
|
|
60
|
+
// daemon started on this exact user-data dir.
|
|
61
|
+
export function killOwnBrowsersOn(dir: string): number {
|
|
62
|
+
let out = '';
|
|
63
|
+
try {
|
|
64
|
+
out = execFileSync('ps', ['-eo', 'pid=,ppid=,args='], { encoding: 'utf8' });
|
|
65
|
+
} catch {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
let killed = 0;
|
|
69
|
+
for (const line of out.split('\n')) {
|
|
70
|
+
const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
71
|
+
// The EXACT dir: a plain substring test matched profile "a" inside "a-b" (measured).
|
|
72
|
+
const flag = `--user-data-dir=${dir}`;
|
|
73
|
+
const at = m ? (` ${m[3]!} `).indexOf(` ${flag} `) : -1;
|
|
74
|
+
if (!m || Number(m[2]) !== process.pid || at < 0) continue;
|
|
75
|
+
try {
|
|
76
|
+
process.kill(Number(m[1]), 'SIGKILL');
|
|
77
|
+
killed++;
|
|
78
|
+
} catch {
|
|
79
|
+
// already gone
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return killed;
|
|
83
|
+
}
|