@oharness/engine 0.7.0 → 0.8.0
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/dist/agent/agent.d.ts +72 -0
- package/dist/agent/agent.js +114 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/permissions.js +8 -0
- package/dist/agent/permissions.js.map +1 -1
- package/dist/browser/cdp.d.ts +75 -0
- package/dist/browser/cdp.js +136 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/chrome.d.ts +19 -0
- package/dist/browser/chrome.js +306 -0
- package/dist/browser/chrome.js.map +1 -0
- package/dist/browser/extension.d.ts +35 -0
- package/dist/browser/extension.js +297 -0
- package/dist/browser/extension.js.map +1 -0
- package/dist/browser/host-manifest.d.ts +78 -0
- package/dist/browser/host-manifest.js +166 -0
- package/dist/browser/host-manifest.js.map +1 -0
- package/dist/browser/host.d.ts +27 -0
- package/dist/browser/host.js +311 -0
- package/dist/browser/host.js.map +1 -0
- package/dist/browser/index.d.ts +26 -0
- package/dist/browser/index.js +15 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/login-import.d.ts +98 -0
- package/dist/browser/login-import.js +244 -0
- package/dist/browser/login-import.js.map +1 -0
- package/dist/browser/plugin.d.ts +109 -0
- package/dist/browser/plugin.js +22 -0
- package/dist/browser/plugin.js.map +1 -0
- package/dist/browser/presence.d.ts +29 -0
- package/dist/browser/presence.js +54 -0
- package/dist/browser/presence.js.map +1 -0
- package/dist/browser/provider.d.ts +17 -0
- package/dist/browser/provider.js +13 -0
- package/dist/browser/provider.js.map +1 -0
- package/dist/browser/screencast.d.ts +42 -0
- package/dist/browser/screencast.js +99 -0
- package/dist/browser/screencast.js.map +1 -0
- package/dist/browser/session.d.ts +320 -0
- package/dist/browser/session.js +837 -0
- package/dist/browser/session.js.map +1 -0
- package/dist/browser/snapshot.d.ts +49 -0
- package/dist/browser/snapshot.js +145 -0
- package/dist/browser/snapshot.js.map +1 -0
- package/dist/browser/socket.d.ts +15 -0
- package/dist/browser/socket.js +32 -0
- package/dist/browser/socket.js.map +1 -0
- package/dist/browser/target.d.ts +112 -0
- package/dist/browser/target.js +20 -0
- package/dist/browser/target.js.map +1 -0
- package/dist/browser/tools.d.ts +3 -0
- package/dist/browser/tools.js +537 -0
- package/dist/browser/tools.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js.map +1 -1
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.js +14 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/harness.d.ts +49 -0
- package/dist/harness.js +97 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/piai.js +7 -2
- package/dist/providers/piai.js.map +1 -1
- package/dist/tools/define.d.ts +1 -0
- package/dist/tools/define.js +3 -0
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/types.d.ts +11 -0
- package/dist/tools/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { BrowserError } from "../core/errors.js";
|
|
2
|
+
import { parseJson } from "../util/json.js";
|
|
3
|
+
export class CdpConnection {
|
|
4
|
+
nextId = 1;
|
|
5
|
+
pending = new Map();
|
|
6
|
+
/** Keyed `sessionId\0event`, or just `event` for browser-level ones. */
|
|
7
|
+
handlers = new Map();
|
|
8
|
+
closedReason;
|
|
9
|
+
write;
|
|
10
|
+
/**
|
|
11
|
+
* `write` takes the message, not a string.
|
|
12
|
+
*
|
|
13
|
+
* One transport frames with a NUL after JSON, another hands objects to a
|
|
14
|
+
* relay that serialises them itself; a connection that stringified here would
|
|
15
|
+
* force the second to parse back what it had just been given.
|
|
16
|
+
*/
|
|
17
|
+
constructor(write) {
|
|
18
|
+
this.write = write;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Send a command and wait for its reply.
|
|
22
|
+
*
|
|
23
|
+
* `sessionId` omitted means the browser itself — `Target.*`, `Browser.*`.
|
|
24
|
+
* Anything touching a page needs one, which is why `BrowserPage` binds it
|
|
25
|
+
* rather than leaving it to each call site.
|
|
26
|
+
*/
|
|
27
|
+
send(method, params = {}, sessionId) {
|
|
28
|
+
if (this.closedReason) {
|
|
29
|
+
return Promise.reject(new BrowserError(`browser connection closed: ${this.closedReason}`));
|
|
30
|
+
}
|
|
31
|
+
const id = this.nextId++;
|
|
32
|
+
const message = { id, method, params };
|
|
33
|
+
if (sessionId)
|
|
34
|
+
message.sessionId = sessionId;
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
this.pending.set(id, { resolve, reject, method });
|
|
37
|
+
try {
|
|
38
|
+
this.write(message);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
this.pending.delete(id);
|
|
42
|
+
reject(new BrowserError(`could not send ${method}`, err));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Subscribe to an event. Returns the unsubscribe. */
|
|
47
|
+
on(event, handler, sessionId) {
|
|
48
|
+
const key = sessionId ? `${sessionId}\0${event}` : event;
|
|
49
|
+
let set = this.handlers.get(key);
|
|
50
|
+
if (!set)
|
|
51
|
+
this.handlers.set(key, (set = new Set()));
|
|
52
|
+
set.add(handler);
|
|
53
|
+
return () => {
|
|
54
|
+
const current = this.handlers.get(key);
|
|
55
|
+
if (!current)
|
|
56
|
+
return;
|
|
57
|
+
current.delete(handler);
|
|
58
|
+
if (current.size === 0)
|
|
59
|
+
this.handlers.delete(key);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** One protocol message, already split out of a text stream. */
|
|
63
|
+
receive(raw) {
|
|
64
|
+
const parsed = parseJson(raw);
|
|
65
|
+
// Chrome does not interleave anything else on this channel, but a provider
|
|
66
|
+
// that multiplexes might; a line we cannot read is not worth a crash.
|
|
67
|
+
if (!parsed.ok)
|
|
68
|
+
return;
|
|
69
|
+
this.deliver(parsed.value);
|
|
70
|
+
}
|
|
71
|
+
/** One protocol message, from a transport that never made it text. */
|
|
72
|
+
deliver(message) {
|
|
73
|
+
if (typeof message.id === "number") {
|
|
74
|
+
const pending = this.pending.get(message.id);
|
|
75
|
+
if (!pending)
|
|
76
|
+
return;
|
|
77
|
+
this.pending.delete(message.id);
|
|
78
|
+
if (message.error) {
|
|
79
|
+
pending.reject(new BrowserError(`${pending.method}: ${message.error.message ?? "protocol error"}`));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
pending.resolve(message.result ?? {});
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!message.method)
|
|
87
|
+
return;
|
|
88
|
+
const key = message.sessionId ? `${message.sessionId}\0${message.method}` : message.method;
|
|
89
|
+
for (const handler of this.handlers.get(key) ?? []) {
|
|
90
|
+
// One bad subscriber must not stop the others, and must not kill the
|
|
91
|
+
// connection: these run on the read path of a live protocol stream.
|
|
92
|
+
try {
|
|
93
|
+
handler(message.params ?? {});
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* ignore */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** The transport went away. Everything still waiting fails with the reason. */
|
|
101
|
+
fail(reason) {
|
|
102
|
+
this.closedReason ??= reason;
|
|
103
|
+
const waiting = [...this.pending.values()];
|
|
104
|
+
this.pending.clear();
|
|
105
|
+
this.handlers.clear();
|
|
106
|
+
for (const pending of waiting) {
|
|
107
|
+
pending.reject(new BrowserError(`${pending.method}: ${reason}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
get closed() {
|
|
111
|
+
return this.closedReason !== undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Split a CDP pipe's bytes into messages.
|
|
116
|
+
*
|
|
117
|
+
* Chrome's `--remote-debugging-pipe` terminates each JSON document with a NUL
|
|
118
|
+
* rather than a newline, because a JSON string may legally contain a newline
|
|
119
|
+
* and may not contain a raw NUL. Kept as a standalone function so the framing
|
|
120
|
+
* is tested directly — a framing bug shows up as a hang, which is the hardest
|
|
121
|
+
* kind of bug to read backwards from.
|
|
122
|
+
*/
|
|
123
|
+
export class NulFramer {
|
|
124
|
+
buffer = "";
|
|
125
|
+
push(chunk, onMessage) {
|
|
126
|
+
this.buffer += chunk;
|
|
127
|
+
let index;
|
|
128
|
+
while ((index = this.buffer.indexOf("\0")) !== -1) {
|
|
129
|
+
const raw = this.buffer.slice(0, index);
|
|
130
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
131
|
+
if (raw.length > 0)
|
|
132
|
+
onMessage(raw);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=cdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../src/browser/cdp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Logger } from "../util/logger.ts";
|
|
2
|
+
import type { BrowserTarget } from "./target.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Find a Chromium to drive.
|
|
5
|
+
*
|
|
6
|
+
* `OHARNESS_BROWSER_PATH` wins, so a person on a distribution nobody listed —
|
|
7
|
+
* or one who wants a specific channel — is never stuck arguing with a hardcoded
|
|
8
|
+
* array.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findChrome(platform?: NodeJS.Platform): Promise<string | undefined>;
|
|
11
|
+
export interface LaunchChromeOptions {
|
|
12
|
+
/** Defaults to whatever `findChrome` turns up. */
|
|
13
|
+
executablePath?: string;
|
|
14
|
+
/** Defaults to a directory under the OHarness home, kept between runs. */
|
|
15
|
+
userDataDir?: string;
|
|
16
|
+
headless?: boolean;
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
}
|
|
19
|
+
export declare function launchChrome(opts?: LaunchChromeOptions): Promise<BrowserTarget>;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access, mkdir } from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { BrowserError } from "../core/errors.js";
|
|
5
|
+
import { userPath } from "../paths.js";
|
|
6
|
+
import { CdpConnection, NulFramer } from "./cdp.js";
|
|
7
|
+
import { ScreencastHub } from "./screencast.js";
|
|
8
|
+
/**
|
|
9
|
+
* A Chrome the harness starts and owns.
|
|
10
|
+
*
|
|
11
|
+
* Two decisions worth stating, because both look like details and neither is.
|
|
12
|
+
*
|
|
13
|
+
* It runs against its own `--user-data-dir`, never the person's. That keeps the
|
|
14
|
+
* agent out of their logged-in sessions, and it is also the only way this works
|
|
15
|
+
* at all: since Chrome 136 remote debugging is refused on the default profile,
|
|
16
|
+
* so a build that tried to drive the everyday browser would simply fail to
|
|
17
|
+
* attach. Reaching a logged-in session is a different feature with a different
|
|
18
|
+
* mechanism — an extension — and it is deliberately not this one.
|
|
19
|
+
*
|
|
20
|
+
* It talks over `--remote-debugging-pipe` rather than a debugging port. A port
|
|
21
|
+
* is a TCP listener on the loopback interface that any local process may
|
|
22
|
+
* connect to, and what it grants is complete control of a browser: reading
|
|
23
|
+
* every page, typing into it, and executing script in it. The pipe is two file
|
|
24
|
+
* descriptors held by this process alone.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_ARGS = [
|
|
27
|
+
"--remote-debugging-pipe",
|
|
28
|
+
"--no-first-run",
|
|
29
|
+
"--no-default-browser-check",
|
|
30
|
+
"--disable-background-networking",
|
|
31
|
+
"--disable-sync",
|
|
32
|
+
"--disable-features=Translate,OptimizationHints",
|
|
33
|
+
// The agent's browser is not where anybody wants to be asked about
|
|
34
|
+
// notifications, and a permission bubble is a modal the model cannot see.
|
|
35
|
+
"--deny-permission-prompts",
|
|
36
|
+
];
|
|
37
|
+
/** Where each platform keeps the browsers, most preferred first. */
|
|
38
|
+
const CANDIDATES = {
|
|
39
|
+
darwin: [
|
|
40
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
41
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
42
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
43
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
44
|
+
],
|
|
45
|
+
linux: [
|
|
46
|
+
"/usr/bin/google-chrome",
|
|
47
|
+
"/usr/bin/google-chrome-stable",
|
|
48
|
+
"/usr/bin/chromium",
|
|
49
|
+
"/usr/bin/chromium-browser",
|
|
50
|
+
"/usr/bin/microsoft-edge",
|
|
51
|
+
],
|
|
52
|
+
win32: [
|
|
53
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
54
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
55
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Find a Chromium to drive.
|
|
60
|
+
*
|
|
61
|
+
* `OHARNESS_BROWSER_PATH` wins, so a person on a distribution nobody listed —
|
|
62
|
+
* or one who wants a specific channel — is never stuck arguing with a hardcoded
|
|
63
|
+
* array.
|
|
64
|
+
*/
|
|
65
|
+
export async function findChrome(platform = process.platform) {
|
|
66
|
+
const override = process.env["OHARNESS_BROWSER_PATH"];
|
|
67
|
+
if (override)
|
|
68
|
+
return (await executable(override)) ? override : undefined;
|
|
69
|
+
for (const candidate of CANDIDATES[platform] ?? []) {
|
|
70
|
+
if (await executable(candidate))
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
async function executable(file) {
|
|
76
|
+
try {
|
|
77
|
+
await access(file, constants.X_OK);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function launchChrome(opts = {}) {
|
|
85
|
+
const bin = opts.executablePath ?? (await findChrome());
|
|
86
|
+
if (!bin) {
|
|
87
|
+
throw new BrowserError("no Chrome, Chromium, Edge or Brave found. Install one, or set OHARNESS_BROWSER_PATH to its executable.");
|
|
88
|
+
}
|
|
89
|
+
const profile = opts.userDataDir ?? userPath("browser-profile");
|
|
90
|
+
await mkdir(profile, { recursive: true });
|
|
91
|
+
const args = [...DEFAULT_ARGS, `--user-data-dir=${profile}`];
|
|
92
|
+
// The new headless mode is a real Chrome with no window, not the old
|
|
93
|
+
// separate binary that behaved differently enough to make a page work in
|
|
94
|
+
// one and fail in the other.
|
|
95
|
+
if (opts.headless !== false)
|
|
96
|
+
args.push("--headless=new");
|
|
97
|
+
args.push("about:blank");
|
|
98
|
+
const child = spawn(bin, args, {
|
|
99
|
+
// fds 3 and 4 are the protocol: Chrome reads commands from 3 and writes
|
|
100
|
+
// events to 4. stdout and stderr stay separate so a crash message is not
|
|
101
|
+
// mistaken for a protocol frame.
|
|
102
|
+
stdio: ["ignore", "pipe", "pipe", "pipe", "pipe"],
|
|
103
|
+
});
|
|
104
|
+
const toBrowser = child.stdio[3];
|
|
105
|
+
const fromBrowser = child.stdio[4];
|
|
106
|
+
if (!toBrowser || !fromBrowser) {
|
|
107
|
+
child.kill();
|
|
108
|
+
throw new BrowserError("chrome started without a protocol pipe");
|
|
109
|
+
}
|
|
110
|
+
const connection = new CdpConnection((message) => {
|
|
111
|
+
toBrowser.write(JSON.stringify(message) + "\0");
|
|
112
|
+
});
|
|
113
|
+
const framer = new NulFramer();
|
|
114
|
+
fromBrowser.setEncoding("utf8");
|
|
115
|
+
fromBrowser.on("data", (chunk) => framer.push(chunk, (raw) => connection.receive(raw)));
|
|
116
|
+
let stderr = "";
|
|
117
|
+
child.stderr?.setEncoding("utf8");
|
|
118
|
+
child.stderr?.on("data", (chunk) => {
|
|
119
|
+
// Kept only to explain a failure to start; a running Chrome is chatty and
|
|
120
|
+
// none of it is interesting.
|
|
121
|
+
if (stderr.length < 4000)
|
|
122
|
+
stderr += chunk;
|
|
123
|
+
});
|
|
124
|
+
const exited = new Promise((_, reject) => {
|
|
125
|
+
child.once("error", (err) => {
|
|
126
|
+
connection.fail(err.message);
|
|
127
|
+
reject(new BrowserError(`could not start ${bin}`, err));
|
|
128
|
+
});
|
|
129
|
+
child.once("exit", (code) => {
|
|
130
|
+
const reason = `browser exited${code === null ? "" : ` with code ${code}`}`;
|
|
131
|
+
connection.fail(reason);
|
|
132
|
+
reject(new BrowserError(stderr.trim() ? `${reason}: ${stderr.trim().split("\n").at(-1)}` : reason));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
// Nothing awaits this until something has already gone wrong; without a
|
|
136
|
+
// sink an early exit would be an unhandled rejection.
|
|
137
|
+
exited.catch(() => { });
|
|
138
|
+
const target = new ChromeTarget(child, connection, opts.logger);
|
|
139
|
+
// Prove the protocol works before handing the target out, so a broken launch
|
|
140
|
+
// is one error here rather than a confusing failure inside the first tool.
|
|
141
|
+
await Promise.race([target.handshake(), exited]);
|
|
142
|
+
return target;
|
|
143
|
+
}
|
|
144
|
+
class ChromeTarget {
|
|
145
|
+
kind = "chrome";
|
|
146
|
+
attached = new Map();
|
|
147
|
+
closing;
|
|
148
|
+
child;
|
|
149
|
+
cdp;
|
|
150
|
+
logger;
|
|
151
|
+
constructor(child, cdp, logger) {
|
|
152
|
+
this.child = child;
|
|
153
|
+
this.cdp = cdp;
|
|
154
|
+
this.logger = logger;
|
|
155
|
+
}
|
|
156
|
+
async handshake() {
|
|
157
|
+
await this.cdp.send("Target.setDiscoverTargets", { discover: true });
|
|
158
|
+
}
|
|
159
|
+
async pages() {
|
|
160
|
+
const result = await this.cdp.send("Target.getTargets");
|
|
161
|
+
const infos = Array.isArray(result["targetInfos"]) ? result["targetInfos"] : [];
|
|
162
|
+
const pages = [];
|
|
163
|
+
for (const info of infos) {
|
|
164
|
+
const record = info;
|
|
165
|
+
if (record["type"] !== "page")
|
|
166
|
+
continue;
|
|
167
|
+
const targetId = String(record["targetId"]);
|
|
168
|
+
pages.push(this.attached.get(targetId) ?? (await this.attach(targetId)));
|
|
169
|
+
}
|
|
170
|
+
return pages;
|
|
171
|
+
}
|
|
172
|
+
async pageInfos() {
|
|
173
|
+
const result = await this.cdp.send("Target.getTargets");
|
|
174
|
+
const infos = Array.isArray(result["targetInfos"]) ? result["targetInfos"] : [];
|
|
175
|
+
return infos.flatMap((info) => {
|
|
176
|
+
const record = info;
|
|
177
|
+
if (record["type"] !== "page")
|
|
178
|
+
return [];
|
|
179
|
+
const title = typeof record["title"] === "string" ? record["title"].trim() : "";
|
|
180
|
+
return [{
|
|
181
|
+
id: String(record["targetId"]),
|
|
182
|
+
url: typeof record["url"] === "string" ? record["url"] : "",
|
|
183
|
+
...(title ? { title } : {}),
|
|
184
|
+
}];
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async pageById(id) {
|
|
188
|
+
const info = (await this.pageInfos()).find((page) => page.id === id);
|
|
189
|
+
return info ? this.attach(id) : undefined;
|
|
190
|
+
}
|
|
191
|
+
async newPage(url = "about:blank") {
|
|
192
|
+
const result = await this.cdp.send("Target.createTarget", { url });
|
|
193
|
+
return this.attach(String(result["targetId"]));
|
|
194
|
+
}
|
|
195
|
+
async activate(page) {
|
|
196
|
+
await this.cdp.send("Target.activateTarget", { targetId: page.id });
|
|
197
|
+
}
|
|
198
|
+
async attach(targetId) {
|
|
199
|
+
const existing = this.attached.get(targetId);
|
|
200
|
+
if (existing)
|
|
201
|
+
return existing;
|
|
202
|
+
const result = await this.cdp.send("Target.attachToTarget", { targetId, flatten: true });
|
|
203
|
+
const sessionId = String(result["sessionId"]);
|
|
204
|
+
const page = new ChromePage(targetId, sessionId, this.cdp, () => {
|
|
205
|
+
this.attached.delete(targetId);
|
|
206
|
+
});
|
|
207
|
+
this.attached.set(targetId, page);
|
|
208
|
+
await page.prepare();
|
|
209
|
+
return page;
|
|
210
|
+
}
|
|
211
|
+
close() {
|
|
212
|
+
return (this.closing ??= this.shutdown());
|
|
213
|
+
}
|
|
214
|
+
async shutdown() {
|
|
215
|
+
// Ask first: a browser told to close flushes its profile, and a profile
|
|
216
|
+
// killed mid-write is one Chrome complains about on the next start.
|
|
217
|
+
try {
|
|
218
|
+
await Promise.race([
|
|
219
|
+
this.cdp.send("Browser.close"),
|
|
220
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* already gone */
|
|
225
|
+
}
|
|
226
|
+
this.cdp.fail("closed");
|
|
227
|
+
if (this.child.exitCode === null)
|
|
228
|
+
this.child.kill();
|
|
229
|
+
this.logger?.debug?.("browser closed");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
class ChromePage {
|
|
233
|
+
id;
|
|
234
|
+
sessionId;
|
|
235
|
+
cdp;
|
|
236
|
+
forget;
|
|
237
|
+
screencast;
|
|
238
|
+
constructor(id, sessionId, cdp, forget) {
|
|
239
|
+
this.id = id;
|
|
240
|
+
this.sessionId = sessionId;
|
|
241
|
+
this.cdp = cdp;
|
|
242
|
+
this.forget = forget;
|
|
243
|
+
this.screencast = new ScreencastHub(this);
|
|
244
|
+
}
|
|
245
|
+
/** The domains every tool assumes are on. */
|
|
246
|
+
async prepare() {
|
|
247
|
+
await Promise.all([
|
|
248
|
+
this.send("Page.enable"),
|
|
249
|
+
this.send("Runtime.enable"),
|
|
250
|
+
this.send("DOM.enable"),
|
|
251
|
+
this.send("Log.enable"),
|
|
252
|
+
]);
|
|
253
|
+
}
|
|
254
|
+
send(method, params = {}) {
|
|
255
|
+
return this.cdp.send(method, params, this.sessionId);
|
|
256
|
+
}
|
|
257
|
+
on(event, handler) {
|
|
258
|
+
return this.cdp.on(event, handler, this.sessionId);
|
|
259
|
+
}
|
|
260
|
+
/** Frames, shared with every other watcher of this page. */
|
|
261
|
+
frames(opts = {}) {
|
|
262
|
+
return this.screencast.frames(opts);
|
|
263
|
+
}
|
|
264
|
+
async input(event) {
|
|
265
|
+
if (event.type === "text") {
|
|
266
|
+
await this.send("Input.insertText", { text: event.text });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (event.type === "key") {
|
|
270
|
+
const method = event.action === "char" ? "char" : event.action === "down" ? "keyDown" : "keyUp";
|
|
271
|
+
await this.send("Input.dispatchKeyEvent", {
|
|
272
|
+
type: method,
|
|
273
|
+
...(event.key ? { key: event.key } : {}),
|
|
274
|
+
...(event.code ? { code: event.code } : {}),
|
|
275
|
+
...(event.text ? { text: event.text } : {}),
|
|
276
|
+
modifiers: event.modifiers ?? 0,
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (event.action === "wheel") {
|
|
281
|
+
await this.send("Input.dispatchMouseEvent", {
|
|
282
|
+
type: "mouseWheel",
|
|
283
|
+
x: event.x,
|
|
284
|
+
y: event.y,
|
|
285
|
+
deltaX: event.deltaX ?? 0,
|
|
286
|
+
deltaY: event.deltaY ?? 0,
|
|
287
|
+
modifiers: event.modifiers ?? 0,
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
await this.send("Input.dispatchMouseEvent", {
|
|
292
|
+
type: event.action === "move" ? "mouseMoved" : event.action === "down" ? "mousePressed" : "mouseReleased",
|
|
293
|
+
x: event.x,
|
|
294
|
+
y: event.y,
|
|
295
|
+
button: event.button ?? "left",
|
|
296
|
+
clickCount: event.clickCount ?? (event.action === "move" ? 0 : 1),
|
|
297
|
+
modifiers: event.modifiers ?? 0,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async close() {
|
|
301
|
+
this.forget();
|
|
302
|
+
this.screencast.close();
|
|
303
|
+
await this.cdp.send("Target.closeTarget", { targetId: this.id }).catch(() => { });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=chrome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../src/browser/chrome.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BrowserTarget, BrowserTargetContext } from "./target.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The user's own Chrome, reached through an extension.
|
|
4
|
+
*
|
|
5
|
+
* This is the only way to drive a browser somebody is already signed into.
|
|
6
|
+
* Chrome has refused remote debugging on the default profile since 136, so
|
|
7
|
+
* attaching to the everyday browser is not a thing a build can do however it
|
|
8
|
+
* asks — and that refusal is the right one, because a debugging port is
|
|
9
|
+
* unauthenticated control of every logged-in session on the machine. An
|
|
10
|
+
* extension is the sanctioned path: the person installs it, Chrome shows a
|
|
11
|
+
* banner for as long as it is driving, and `chrome.debugger` gives the same CDP
|
|
12
|
+
* everything else here already speaks.
|
|
13
|
+
*
|
|
14
|
+
* So the extension is a relay and almost nothing else. It forwards CDP in both
|
|
15
|
+
* directions and answers a handful of control messages that CDP has no browser
|
|
16
|
+
* half for — listing tabs, opening or activating one, attaching. The same `CdpConnection` that
|
|
17
|
+
* drives a piped Chrome drives this, with the tab id standing in for the session
|
|
18
|
+
* id, which is what it already is.
|
|
19
|
+
*
|
|
20
|
+
* The bridge in between is a local socket rather than an HTTP endpoint on the
|
|
21
|
+
* harness server. Chrome spawns the native messaging host, so the host is the
|
|
22
|
+
* process that exists first; having it listen and the harness connect means the
|
|
23
|
+
* feature needs no server running, no token to plumb, and works the same from
|
|
24
|
+
* the TUI as from the desktop app.
|
|
25
|
+
*/
|
|
26
|
+
/** What the target needs of the thing between it and the extension. */
|
|
27
|
+
export interface ExtensionChannel {
|
|
28
|
+
send(message: unknown): void;
|
|
29
|
+
onMessage(handler: (message: unknown) => void): void;
|
|
30
|
+
onClose(handler: (reason?: string) => void): void;
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Open the socket the native messaging host listens on. */
|
|
34
|
+
export declare function connectExtensionChannel(path?: string): Promise<ExtensionChannel>;
|
|
35
|
+
export declare function extensionTarget(channel?: ExtensionChannel, context?: BrowserTargetContext): Promise<BrowserTarget>;
|