@houwert/conductor 0.23.0 → 0.24.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.
|
@@ -16,6 +16,7 @@ const bootstrap_js_1 = require("../drivers/bootstrap.js");
|
|
|
16
16
|
const client_js_1 = require("../daemon/client.js");
|
|
17
17
|
const protocol_js_1 = require("../daemon/protocol.js");
|
|
18
18
|
const cli_js_1 = require("../drivers/vega/cli.js");
|
|
19
|
+
const cdp_discovery_js_1 = require("../drivers/cdp-discovery.js");
|
|
19
20
|
// Captured during discoverAvailableDevices so listDevices() can report it.
|
|
20
21
|
// Module-scoped because the discover function returns Device[]; bolting an
|
|
21
22
|
// extra return field onto the public type would ripple beyond this fix.
|
|
@@ -104,6 +105,9 @@ async function discoverBootedDevices() {
|
|
|
104
105
|
catch {
|
|
105
106
|
/* vega CLI not installed */
|
|
106
107
|
}
|
|
108
|
+
// External CDP endpoints (e.g. an Electron app started with
|
|
109
|
+
// --remote-debugging-port): one device per discovered webview/tile.
|
|
110
|
+
devices.push(...(await (0, cdp_discovery_js_1.discoverCdpDevices)()));
|
|
107
111
|
return devices;
|
|
108
112
|
}
|
|
109
113
|
async function discoverAvailableDevices() {
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.HELP = void 0;
|
|
7
4
|
exports.webTargets = webTargets;
|
|
@@ -14,37 +11,8 @@ exports.webTargets = webTargets;
|
|
|
14
11
|
* Playwright browser and works before any daemon session exists. Use the printed
|
|
15
12
|
* target IDs with `--cdp-url` / `--cdp-target` to bind a session to a tile.
|
|
16
13
|
*/
|
|
17
|
-
const http_1 = __importDefault(require("http"));
|
|
18
14
|
const output_js_1 = require("../output.js");
|
|
19
|
-
|
|
20
|
-
function httpBase(cdpUrl) {
|
|
21
|
-
const u = new URL(cdpUrl);
|
|
22
|
-
const proto = u.protocol === 'https:' || u.protocol === 'wss:' ? 'https:' : 'http:';
|
|
23
|
-
return `${proto}//${u.host}`;
|
|
24
|
-
}
|
|
25
|
-
function fetchTargets(cdpUrl) {
|
|
26
|
-
const url = `${httpBase(cdpUrl)}/json/list`;
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const req = http_1.default.get(url, (res) => {
|
|
29
|
-
const chunks = [];
|
|
30
|
-
res.on('data', (c) => chunks.push(c));
|
|
31
|
-
res.on('end', () => {
|
|
32
|
-
if ((res.statusCode ?? 0) >= 300) {
|
|
33
|
-
reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8')));
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
reject(err);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
req.setTimeout(5000, () => req.destroy(new Error(`Timed out fetching ${url}`)));
|
|
45
|
-
req.on('error', reject);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
15
|
+
const cdp_discovery_js_1 = require("../drivers/cdp-discovery.js");
|
|
48
16
|
async function webTargets(cdpUrl, opts) {
|
|
49
17
|
if (!cdpUrl) {
|
|
50
18
|
console.error('web-targets requires --cdp-url <url> (e.g. --cdp-url http://127.0.0.1:9222).\n' +
|
|
@@ -53,7 +21,7 @@ async function webTargets(cdpUrl, opts) {
|
|
|
53
21
|
}
|
|
54
22
|
let targets;
|
|
55
23
|
try {
|
|
56
|
-
targets = await
|
|
24
|
+
targets = await (0, cdp_discovery_js_1.fetchCdpTargets)(cdpUrl);
|
|
57
25
|
}
|
|
58
26
|
catch (err) {
|
|
59
27
|
console.error(`Could not reach CDP endpoint at ${cdpUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_CDP_PORTS = void 0;
|
|
7
|
+
exports.httpBase = httpBase;
|
|
8
|
+
exports.fetchCdpTargets = fetchCdpTargets;
|
|
9
|
+
exports.formatCdpDeviceId = formatCdpDeviceId;
|
|
10
|
+
exports.parseCdpDeviceId = parseCdpDeviceId;
|
|
11
|
+
exports.cdpTargetsToDevices = cdpTargetsToDevices;
|
|
12
|
+
exports.discoverCdpDevices = discoverCdpDevices;
|
|
13
|
+
/**
|
|
14
|
+
* Discovery of externally-launched CDP endpoints (e.g. an Electron app started
|
|
15
|
+
* with `--remote-debugging-port`, which exposes one page target per webview/tile).
|
|
16
|
+
*
|
|
17
|
+
* Unlike the Playwright web driver (which conductor launches itself) these
|
|
18
|
+
* browsers already exist — so "discovery" means finding the DevTools HTTP
|
|
19
|
+
* endpoint. CDP servers don't advertise themselves, so we probe a small range of
|
|
20
|
+
* localhost ports and enumerate each reachable endpoint's page targets via
|
|
21
|
+
* `/json/list`. The same target-fetch is reused by the `web-targets` command.
|
|
22
|
+
*/
|
|
23
|
+
const http_1 = __importDefault(require("http"));
|
|
24
|
+
/** Default localhost ports scanned for CDP endpoints. Covers Chromium/Electron's
|
|
25
|
+
* conventional `--remote-debugging-port` values without a wide scan. */
|
|
26
|
+
exports.DEFAULT_CDP_PORTS = [9222, 9223, 9224, 9225, 9226, 9227, 9228, 9229];
|
|
27
|
+
const PROBE_TIMEOUT_MS = 300;
|
|
28
|
+
/** Derive the `http://host:port` base from a CDP URL (which may be ws:// or include a path). */
|
|
29
|
+
function httpBase(cdpUrl) {
|
|
30
|
+
const u = new URL(cdpUrl);
|
|
31
|
+
const proto = u.protocol === 'https:' || u.protocol === 'wss:' ? 'https:' : 'http:';
|
|
32
|
+
return `${proto}//${u.host}`;
|
|
33
|
+
}
|
|
34
|
+
/** GET a CDP DevTools JSON endpoint, parsing the array response. */
|
|
35
|
+
function getJson(url, timeoutMs) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const req = http_1.default.get(url, (res) => {
|
|
38
|
+
const chunks = [];
|
|
39
|
+
res.on('data', (c) => chunks.push(c));
|
|
40
|
+
res.on('end', () => {
|
|
41
|
+
if ((res.statusCode ?? 0) >= 300) {
|
|
42
|
+
reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8')));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
reject(err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
req.setTimeout(timeoutMs, () => req.destroy(new Error(`Timed out fetching ${url}`)));
|
|
54
|
+
req.on('error', reject);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Fetch the CDP page targets exposed at `cdpUrl`'s `/json/list`. */
|
|
58
|
+
function fetchCdpTargets(cdpUrl, timeoutMs = 5000) {
|
|
59
|
+
return getJson(`${httpBase(cdpUrl)}/json/list`, timeoutMs);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Device-id encoding for a discovered CDP webview: `web:cdp:<port>:<targetId>`.
|
|
63
|
+
* Self-describing so the id alone hydrates the CDP url + target at bind time —
|
|
64
|
+
* a discovered webview is drivable with no `--cdp-*` flags. Localhost is assumed
|
|
65
|
+
* (discovery only scans localhost).
|
|
66
|
+
*/
|
|
67
|
+
function formatCdpDeviceId(port, targetId) {
|
|
68
|
+
return `web:cdp:${port}:${targetId}`;
|
|
69
|
+
}
|
|
70
|
+
/** Parse a `web:cdp:<port>:<targetId>` device id, or undefined if it isn't one. */
|
|
71
|
+
function parseCdpDeviceId(deviceId) {
|
|
72
|
+
const m = /^web:cdp:(\d+):(.+)$/.exec(deviceId);
|
|
73
|
+
if (!m)
|
|
74
|
+
return undefined;
|
|
75
|
+
const port = Number(m[1]);
|
|
76
|
+
if (!Number.isInteger(port) || port <= 0)
|
|
77
|
+
return undefined;
|
|
78
|
+
return { port, targetId: m[2], cdpUrl: `http://127.0.0.1:${port}` };
|
|
79
|
+
}
|
|
80
|
+
/** Map a reachable endpoint's page targets to discovered `web` devices. */
|
|
81
|
+
function cdpTargetsToDevices(port, targets) {
|
|
82
|
+
return targets
|
|
83
|
+
.filter((t) => t.type === 'page')
|
|
84
|
+
.map((t) => ({
|
|
85
|
+
id: formatCdpDeviceId(port, t.id),
|
|
86
|
+
name: t.title || t.url || t.id,
|
|
87
|
+
platform: 'web',
|
|
88
|
+
status: 'running',
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Scan localhost CDP ports and return every discovered webview as a device.
|
|
93
|
+
* Probes run in parallel with a short timeout and swallow all errors — an
|
|
94
|
+
* unreachable port simply contributes nothing. Safe to call on the hot device-
|
|
95
|
+
* resolution path.
|
|
96
|
+
*/
|
|
97
|
+
async function discoverCdpDevices(ports = exports.DEFAULT_CDP_PORTS) {
|
|
98
|
+
const results = await Promise.all(ports.map(async (port) => {
|
|
99
|
+
try {
|
|
100
|
+
const targets = await fetchCdpTargets(`http://127.0.0.1:${port}`, PROBE_TIMEOUT_MS);
|
|
101
|
+
return cdpTargetsToDevices(port, targets);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
return results.flat();
|
|
108
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,7 @@ const options_js_1 = require("./commands/options.js");
|
|
|
64
64
|
const web_targets_js_1 = require("./commands/web-targets.js");
|
|
65
65
|
const session_js_2 = require("./session.js");
|
|
66
66
|
const device_picker_js_1 = require("./device-picker.js");
|
|
67
|
+
const cdp_discovery_js_1 = require("./drivers/cdp-discovery.js");
|
|
67
68
|
const update_check_js_1 = require("./update-check.js");
|
|
68
69
|
const pkg_root_js_1 = require("./pkg-root.js");
|
|
69
70
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -326,6 +327,9 @@ async function main() {
|
|
|
326
327
|
if (isWebSession && !NO_DEVICE_COMMANDS.has(command)) {
|
|
327
328
|
const cdpUrlFlag = argv['cdp-url'];
|
|
328
329
|
const cdpTargetFlag = argv['cdp-target'];
|
|
330
|
+
// A discovered `web:cdp:<port>:<target>` device id is self-describing —
|
|
331
|
+
// derive the CDP url/target from it so it's drivable with no --cdp-* flags.
|
|
332
|
+
const fromDeviceId = (0, cdp_discovery_js_1.parseCdpDeviceId)(sessionName);
|
|
329
333
|
if (cdpUrlFlag || cdpTargetFlag) {
|
|
330
334
|
if (cdpUrlFlag)
|
|
331
335
|
process.env.CONDUCTOR_CDP_URL = cdpUrlFlag;
|
|
@@ -336,6 +340,13 @@ async function main() {
|
|
|
336
340
|
cdpTargetId: process.env.CONDUCTOR_CDP_TARGET_ID,
|
|
337
341
|
}, sessionName);
|
|
338
342
|
}
|
|
343
|
+
else if (fromDeviceId) {
|
|
344
|
+
if (!process.env.CONDUCTOR_CDP_URL)
|
|
345
|
+
process.env.CONDUCTOR_CDP_URL = fromDeviceId.cdpUrl;
|
|
346
|
+
if (!process.env.CONDUCTOR_CDP_TARGET_ID) {
|
|
347
|
+
process.env.CONDUCTOR_CDP_TARGET_ID = fromDeviceId.targetId;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
339
350
|
else {
|
|
340
351
|
const saved = await (0, session_js_2.getSession)(sessionName);
|
|
341
352
|
if (saved.cdpUrl && !process.env.CONDUCTOR_CDP_URL) {
|
package/package.json
CHANGED
|
@@ -47,6 +47,13 @@ Use a distinct fully-qualified `--device web:chromium:<label>` per target (a bar
|
|
|
47
47
|
several webviews can be driven concurrently. Only `type=page` targets are
|
|
48
48
|
controllable. See [Web testing → Attaching to an existing browser](../../../docs/web.md).
|
|
49
49
|
|
|
50
|
+
**Discovery:** endpoints on the conventional debugging ports (9222–9229 on
|
|
51
|
+
localhost) are found automatically — `list-devices` shows each webview as a
|
|
52
|
+
booted `web:cdp:<port>:<targetId>` device. That id is self-describing, so you can
|
|
53
|
+
drive it directly (`conductor --device web:cdp:9222:<targetId> <cmd>`) without
|
|
54
|
+
`--cdp-url`/`--cdp-target`. Use `web-targets --cdp-url <url>` for endpoints on a
|
|
55
|
+
non-default port, or to list targets before binding.
|
|
56
|
+
|
|
50
57
|
### Vega (Amazon Fire TV)
|
|
51
58
|
|
|
52
59
|
Vega is a React Native OS driven through Amazon's own `vega`/`kepler` CLI (not
|