@ricsam/r5d-browser 0.0.136 → 0.0.138
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/README.md +7 -33
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/browser-input.mjs +56 -1
- package/dist/mjs/cli-options.mjs +1 -0
- package/dist/mjs/main.mjs +25 -396
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal-diagnostics.mjs +24 -0
- package/dist/mjs/personal-main.mjs +159 -0
- package/dist/mjs/personal-port-forward.mjs +183 -0
- package/dist/mjs/personal-relay-client.mjs +33 -0
- package/dist/mjs/personal-relay-shutdown.mjs +46 -0
- package/dist/mjs/runtime/browser-executor.mjs +11 -0
- package/dist/mjs/runtime/chromium-factory.mjs +101 -0
- package/dist/mjs/runtime/ipc.mjs +128 -0
- package/dist/mjs/runtime/isolated-root.mjs +53 -0
- package/dist/mjs/runtime/keeper.mjs +169 -0
- package/dist/mjs/runtime/ledger.mjs +193 -0
- package/dist/mjs/runtime/main.mjs +105 -0
- package/dist/mjs/runtime/private-paths.mjs +48 -0
- package/dist/mjs/runtime/protocol.mjs +72 -0
- package/dist/types/browser-input.d.ts +4 -0
- package/dist/types/cli-options.d.ts +1 -0
- package/dist/types/personal-diagnostics.d.ts +13 -0
- package/dist/types/personal-main.d.ts +9 -0
- package/dist/types/personal-port-forward.d.ts +45 -0
- package/dist/types/personal-relay-client.d.ts +51 -0
- package/dist/types/personal-relay-shutdown.d.ts +19 -0
- package/dist/types/runtime/browser-executor.d.ts +8 -0
- package/dist/types/runtime/chromium-factory.d.ts +25 -0
- package/dist/types/runtime/fixtures/counter-main.d.ts +1 -0
- package/dist/types/runtime/fixtures/counter-runtime.d.ts +7 -0
- package/dist/types/runtime/ipc.d.ts +23 -0
- package/dist/types/runtime/isolated-root.d.ts +12 -0
- package/dist/types/runtime/keeper.d.ts +44 -0
- package/dist/types/runtime/ledger.d.ts +41 -0
- package/dist/types/runtime/main.d.ts +23 -0
- package/dist/types/runtime/private-paths.d.ts +4 -0
- package/dist/types/runtime/protocol.d.ts +228 -0
- package/package.json +6 -5
- package/dist/cjs/browser-input.cjs +0 -87
- package/dist/cjs/browser-runtime.cjs +0 -708
- package/dist/cjs/chrome-launcher.cjs +0 -701
- package/dist/cjs/cli-options.cjs +0 -95
- package/dist/cjs/main.cjs +0 -427
- package/dist/cjs/password-safe-snapshot.cjs +0 -150
- package/dist/cjs/port-forward-manager.cjs +0 -233
- package/dist/cjs/upload-store.cjs +0 -136
- package/dist/mjs/port-forward-manager.mjs +0 -199
- package/dist/types/port-forward-manager.d.ts +0 -23
package/dist/cjs/cli-options.cjs
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var cli_options_exports = {};
|
|
30
|
-
__export(cli_options_exports, {
|
|
31
|
-
defaultConfigPath: () => defaultConfigPath,
|
|
32
|
-
parseBrowserArgs: () => parseBrowserArgs
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(cli_options_exports);
|
|
35
|
-
var import_node_os = __toESM(require("node:os"), 1);
|
|
36
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
37
|
-
function defaultConfigPath() {
|
|
38
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".config", "r5d", "r5dctl", "config.json");
|
|
39
|
-
}
|
|
40
|
-
function parseBrowserArgs(argv) {
|
|
41
|
-
const options = { configPath: defaultConfigPath(), help: false, version: false };
|
|
42
|
-
const positionals = [];
|
|
43
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
44
|
-
const arg = argv[index];
|
|
45
|
-
if (!arg) continue;
|
|
46
|
-
if (arg === "-h" || arg === "--help") {
|
|
47
|
-
options.help = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (arg === "-v" || arg === "--version") {
|
|
51
|
-
options.version = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const readOption = (name) => {
|
|
55
|
-
if (arg === name) {
|
|
56
|
-
index += 1;
|
|
57
|
-
const value = argv[index];
|
|
58
|
-
if (!value || value.startsWith("--")) throw new Error(`Missing value for ${name}`);
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
return arg.startsWith(`${name}=`) ? arg.slice(name.length + 1) : void 0;
|
|
62
|
-
};
|
|
63
|
-
const entries = [
|
|
64
|
-
["--base-url", "baseUrl"],
|
|
65
|
-
["--token", "token"],
|
|
66
|
-
["--api-key", "apiKey"],
|
|
67
|
-
["--config", "configPath"],
|
|
68
|
-
["--profile", "profilePath"],
|
|
69
|
-
["--chrome-path", "chromePath"]
|
|
70
|
-
];
|
|
71
|
-
let matched = false;
|
|
72
|
-
for (const [name, key] of entries) {
|
|
73
|
-
const value = readOption(name);
|
|
74
|
-
if (value !== void 0) {
|
|
75
|
-
if (!value) throw new Error(`Missing value for ${name}`);
|
|
76
|
-
options[key] = value;
|
|
77
|
-
matched = true;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (matched) continue;
|
|
82
|
-
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
83
|
-
positionals.push(arg);
|
|
84
|
-
}
|
|
85
|
-
if (options.version) return { command: "version", options };
|
|
86
|
-
if (options.help || positionals.length === 0) return { command: "help", options };
|
|
87
|
-
if (positionals[0] !== "start") throw new Error(`Unknown command: ${positionals[0]}`);
|
|
88
|
-
if (positionals.length > 1) throw new Error(`Unexpected argument: ${positionals[1]}`);
|
|
89
|
-
return { command: "start", options };
|
|
90
|
-
}
|
|
91
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
-
0 && (module.exports = {
|
|
93
|
-
defaultConfigPath,
|
|
94
|
-
parseBrowserArgs
|
|
95
|
-
});
|
package/dist/cjs/main.cjs
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
26
|
-
var import_node_os = __toESM(require("node:os"), 1);
|
|
27
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
28
|
-
var import_ws = __toESM(require("ws"), 1);
|
|
29
|
-
var import_browser_runtime = require("./browser-runtime.cjs");
|
|
30
|
-
var import_cli_options = require("./cli-options.cjs");
|
|
31
|
-
var import_chrome_launcher = require("./chrome-launcher.cjs");
|
|
32
|
-
var import_port_forward_manager = require("./port-forward-manager.cjs");
|
|
33
|
-
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
34
|
-
const PACKAGE_NAME = "@ricsam/r5d-browser";
|
|
35
|
-
const RECONNECT_DELAY_MS = 2e3;
|
|
36
|
-
const SERVER_HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
37
|
-
const CHROME_DISCONNECT_EXIT_GRACE_MS = 2e3;
|
|
38
|
-
function browserRoot() {
|
|
39
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "browser");
|
|
40
|
-
}
|
|
41
|
-
function printHelp() {
|
|
42
|
-
process.stdout.write(`Usage:
|
|
43
|
-
r5d-browser start [--base-url <url>] [--token <token>] [--api-key <key>]
|
|
44
|
-
r5d-browser --version
|
|
45
|
-
|
|
46
|
-
Options:
|
|
47
|
-
--base-url <url> r5d.dev base URL (default from r5dctl config or ${DEFAULT_BASE_URL})
|
|
48
|
-
--token <token> r5d device credential
|
|
49
|
-
--api-key <key> r5d API key
|
|
50
|
-
--config <path> Shared r5dctl config path
|
|
51
|
-
--profile <path> Google Chrome profile path (default ~/.r5d/browser/chrome-profile)
|
|
52
|
-
--chrome-path <path> Google Chrome executable (default: auto-detect stable Chrome)
|
|
53
|
-
-v, --version Show r5d-browser version
|
|
54
|
-
-h, --help Show this help
|
|
55
|
-
`);
|
|
56
|
-
}
|
|
57
|
-
function findVersion() {
|
|
58
|
-
let current = process.argv[1] ? import_node_path.default.dirname(import_node_path.default.resolve(process.argv[1])) : process.cwd();
|
|
59
|
-
for (let index = 0; index < 12; index += 1) {
|
|
60
|
-
try {
|
|
61
|
-
const parsed = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(current, "package.json"), "utf8"));
|
|
62
|
-
if (parsed.name === PACKAGE_NAME && parsed.version) return parsed.version;
|
|
63
|
-
} catch {
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const sourceVersion = import_node_fs.default.readFileSync(import_node_path.default.join(current, "VERSION.txt"), "utf8").trim();
|
|
67
|
-
if (sourceVersion && import_node_path.default.basename(current) === "packages") return sourceVersion;
|
|
68
|
-
} catch {
|
|
69
|
-
}
|
|
70
|
-
const parent = import_node_path.default.dirname(current);
|
|
71
|
-
if (parent === current) break;
|
|
72
|
-
current = parent;
|
|
73
|
-
}
|
|
74
|
-
return "unknown";
|
|
75
|
-
}
|
|
76
|
-
function readConfig(configPath) {
|
|
77
|
-
if (!import_node_fs.default.existsSync(configPath)) return {};
|
|
78
|
-
const parsed = JSON.parse(import_node_fs.default.readFileSync(configPath, "utf8"));
|
|
79
|
-
return {
|
|
80
|
-
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
81
|
-
token: typeof parsed.token === "string" ? parsed.token : void 0,
|
|
82
|
-
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function resolveConnection(options) {
|
|
86
|
-
const config = readConfig(options.configPath);
|
|
87
|
-
const token = options.token ?? process.env.R5D_BROWSER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
|
|
88
|
-
if (!token) throw new Error("Authentication required. Run `r5dctl auth login` or pass --token/--api-key.");
|
|
89
|
-
return {
|
|
90
|
-
baseUrl: (options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL).replace(
|
|
91
|
-
/\/+$/,
|
|
92
|
-
""
|
|
93
|
-
),
|
|
94
|
-
token
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function loadInstanceId() {
|
|
98
|
-
const instancePath = import_node_path.default.join(browserRoot(), "instance-id");
|
|
99
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(instancePath), { recursive: true });
|
|
100
|
-
try {
|
|
101
|
-
const id2 = import_node_fs.default.readFileSync(instancePath, "utf8").trim();
|
|
102
|
-
if (id2) return id2;
|
|
103
|
-
} catch {
|
|
104
|
-
}
|
|
105
|
-
const id = crypto.randomUUID();
|
|
106
|
-
import_node_fs.default.writeFileSync(instancePath, `${id}
|
|
107
|
-
`, { mode: 384 });
|
|
108
|
-
return id;
|
|
109
|
-
}
|
|
110
|
-
async function waitForReconnectDelay(signal) {
|
|
111
|
-
if (signal.aborted) return;
|
|
112
|
-
await new Promise((resolve) => {
|
|
113
|
-
const finish = () => {
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
signal.removeEventListener("abort", finish);
|
|
116
|
-
resolve();
|
|
117
|
-
};
|
|
118
|
-
const timer = setTimeout(finish, RECONNECT_DELAY_MS);
|
|
119
|
-
signal.addEventListener("abort", finish, { once: true });
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
async function connectLoop(params) {
|
|
123
|
-
while (!params.signal.aborted) {
|
|
124
|
-
const url = new URL("/browser/ws", params.baseUrl);
|
|
125
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
126
|
-
url.searchParams.set("instance", params.instanceId);
|
|
127
|
-
url.searchParams.set("version", findVersion());
|
|
128
|
-
let socket = null;
|
|
129
|
-
let watchdog;
|
|
130
|
-
const closeForAbort = () => socket?.close(1e3, "r5d-browser shutting down");
|
|
131
|
-
try {
|
|
132
|
-
socket = new import_ws.default(url, { headers: { Authorization: `Bearer ${params.token}` } });
|
|
133
|
-
socket.on("error", () => void 0);
|
|
134
|
-
params.signal.addEventListener("abort", closeForAbort, { once: true });
|
|
135
|
-
await new Promise((resolve, reject) => {
|
|
136
|
-
socket.addEventListener("open", () => resolve(), { once: true });
|
|
137
|
-
socket.addEventListener("error", () => reject(new Error("WebSocket connection failed.")), { once: true });
|
|
138
|
-
socket.addEventListener("close", () => reject(new Error("WebSocket closed before connecting.")), { once: true });
|
|
139
|
-
});
|
|
140
|
-
if (params.signal.aborted) break;
|
|
141
|
-
process.stdout.write(`[r5d-browser] connected to ${params.baseUrl}
|
|
142
|
-
`);
|
|
143
|
-
socket.send(
|
|
144
|
-
JSON.stringify({
|
|
145
|
-
type: "hello",
|
|
146
|
-
hostInfo: {
|
|
147
|
-
hostname: import_node_os.default.hostname(),
|
|
148
|
-
platform: process.platform,
|
|
149
|
-
arch: process.arch,
|
|
150
|
-
version: findVersion(),
|
|
151
|
-
chromiumVersion: params.chromiumVersion,
|
|
152
|
-
profilePath: params.profilePath,
|
|
153
|
-
capabilities: {
|
|
154
|
-
portForwarding: true,
|
|
155
|
-
runJsArtifacts: true,
|
|
156
|
-
readPage: true,
|
|
157
|
-
passwordRedaction: true,
|
|
158
|
-
refActions: true,
|
|
159
|
-
waitFor: true,
|
|
160
|
-
formInput: true,
|
|
161
|
-
typedKeys: true,
|
|
162
|
-
screenshotFullPage: true,
|
|
163
|
-
viewportSizing: true,
|
|
164
|
-
fileUploads: true
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
tabs: await params.runtime.listTabs(),
|
|
168
|
-
portForwards: params.portForwards.list()
|
|
169
|
-
})
|
|
170
|
-
);
|
|
171
|
-
let lastServerMessageAt = Date.now();
|
|
172
|
-
watchdog = setInterval(() => {
|
|
173
|
-
if (Date.now() - lastServerMessageAt >= SERVER_HEARTBEAT_TIMEOUT_MS) socket?.close();
|
|
174
|
-
}, 5e3);
|
|
175
|
-
await new Promise((resolve) => {
|
|
176
|
-
socket.addEventListener("message", (event) => {
|
|
177
|
-
if (params.signal.aborted) return;
|
|
178
|
-
lastServerMessageAt = Date.now();
|
|
179
|
-
void (async () => {
|
|
180
|
-
const message = JSON.parse(String(event.data));
|
|
181
|
-
if (message.type === "ping") {
|
|
182
|
-
socket?.send(JSON.stringify({ type: "pong" }));
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (message.type === "port_forward_start") {
|
|
186
|
-
try {
|
|
187
|
-
await params.portForwards.start(message.forward);
|
|
188
|
-
socket?.send(JSON.stringify({ type: "port_forward_result", requestId: message.requestId, forward: message.forward }));
|
|
189
|
-
} catch (error) {
|
|
190
|
-
socket?.send(
|
|
191
|
-
JSON.stringify({
|
|
192
|
-
type: "port_forward_result",
|
|
193
|
-
requestId: message.requestId,
|
|
194
|
-
error: error instanceof Error ? error.message : String(error)
|
|
195
|
-
})
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (message.type === "port_forward_stop") {
|
|
201
|
-
try {
|
|
202
|
-
await params.portForwards.stop(message.forwardId);
|
|
203
|
-
socket?.send(JSON.stringify({ type: "port_forward_result", requestId: message.requestId }));
|
|
204
|
-
} catch (error) {
|
|
205
|
-
socket?.send(
|
|
206
|
-
JSON.stringify({
|
|
207
|
-
type: "port_forward_result",
|
|
208
|
-
requestId: message.requestId,
|
|
209
|
-
error: error instanceof Error ? error.message : String(error)
|
|
210
|
-
})
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (message.type !== "operation") return;
|
|
216
|
-
try {
|
|
217
|
-
if (["upload_begin", "upload_chunk", "upload_abort"].includes(message.action)) {
|
|
218
|
-
const result2 = await params.runtime.execute(message.action, message.input);
|
|
219
|
-
socket?.send(JSON.stringify({ type: "operation_result", requestId: message.requestId, result: result2 }));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const beforeTabs = await params.runtime.listTabs();
|
|
223
|
-
const operationResult = await params.runtime.execute(message.action, message.input);
|
|
224
|
-
const tabs = await params.runtime.listTabs();
|
|
225
|
-
const beforeIds = new Set(beforeTabs.map((tab) => tab.tabId));
|
|
226
|
-
const openedTabs = tabs.filter((tab) => !beforeIds.has(tab.tabId));
|
|
227
|
-
const result = operationResult && typeof operationResult === "object" ? { ...operationResult, openedTabs } : { result: operationResult, openedTabs };
|
|
228
|
-
socket?.send(JSON.stringify({ type: "operation_result", requestId: message.requestId, result }));
|
|
229
|
-
socket?.send(JSON.stringify({ type: "tabs_snapshot", tabs }));
|
|
230
|
-
} catch (error) {
|
|
231
|
-
socket?.send(
|
|
232
|
-
JSON.stringify({
|
|
233
|
-
type: "operation_result",
|
|
234
|
-
requestId: message.requestId,
|
|
235
|
-
error: error instanceof Error ? error.message : String(error)
|
|
236
|
-
})
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
})();
|
|
240
|
-
});
|
|
241
|
-
socket.addEventListener("close", () => resolve(), { once: true });
|
|
242
|
-
});
|
|
243
|
-
} catch (error) {
|
|
244
|
-
if (!params.signal.aborted) process.stderr.write(`[r5d-browser] ${error instanceof Error ? error.message : String(error)}
|
|
245
|
-
`);
|
|
246
|
-
} finally {
|
|
247
|
-
if (watchdog) clearInterval(watchdog);
|
|
248
|
-
params.signal.removeEventListener("abort", closeForAbort);
|
|
249
|
-
socket?.close();
|
|
250
|
-
}
|
|
251
|
-
if (params.signal.aborted) break;
|
|
252
|
-
process.stdout.write(`[r5d-browser] disconnected; reconnecting...
|
|
253
|
-
`);
|
|
254
|
-
await waitForReconnectDelay(params.signal);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function isProcessAlive(pid) {
|
|
258
|
-
try {
|
|
259
|
-
process.kill(pid, 0);
|
|
260
|
-
return true;
|
|
261
|
-
} catch (error) {
|
|
262
|
-
return error.code === "EPERM";
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function waitUpTo(promise, timeoutMs) {
|
|
266
|
-
let timer;
|
|
267
|
-
try {
|
|
268
|
-
return await Promise.race([
|
|
269
|
-
promise.then(() => true),
|
|
270
|
-
new Promise((resolve) => {
|
|
271
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
272
|
-
})
|
|
273
|
-
]);
|
|
274
|
-
} finally {
|
|
275
|
-
if (timer) clearTimeout(timer);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function resultWithin(promise, timeoutMs) {
|
|
279
|
-
let timer;
|
|
280
|
-
try {
|
|
281
|
-
return await Promise.race([
|
|
282
|
-
promise,
|
|
283
|
-
new Promise((resolve) => {
|
|
284
|
-
timer = setTimeout(() => resolve(void 0), timeoutMs);
|
|
285
|
-
})
|
|
286
|
-
]);
|
|
287
|
-
} finally {
|
|
288
|
-
if (timer) clearTimeout(timer);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function start(options) {
|
|
292
|
-
if (process.platform !== "darwin") throw new Error("r5d-browser currently supports macOS only.");
|
|
293
|
-
const { baseUrl, token } = resolveConnection(options);
|
|
294
|
-
const requestedProfilePath = import_node_path.default.resolve(options.profilePath ?? (0, import_chrome_launcher.defaultChromeProfilePath)());
|
|
295
|
-
const downloadsPath = import_node_path.default.join(browserRoot(), "downloads");
|
|
296
|
-
const playwrightDownloadsPath = import_node_path.default.join(downloadsPath, ".playwright");
|
|
297
|
-
import_node_fs.default.mkdirSync(playwrightDownloadsPath, { recursive: true, mode: 448 });
|
|
298
|
-
let requestedSignalOutcome;
|
|
299
|
-
let requestStop = () => void 0;
|
|
300
|
-
const requestedStop = new Promise((resolve) => {
|
|
301
|
-
requestStop = resolve;
|
|
302
|
-
});
|
|
303
|
-
const signalHandlers = /* @__PURE__ */ new Map();
|
|
304
|
-
const processEvents = process;
|
|
305
|
-
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
306
|
-
const handler = () => {
|
|
307
|
-
requestedSignalOutcome ??= { exitCode: 0 };
|
|
308
|
-
requestStop(requestedSignalOutcome);
|
|
309
|
-
};
|
|
310
|
-
signalHandlers.set(signal, handler);
|
|
311
|
-
process.on(signal, handler);
|
|
312
|
-
}
|
|
313
|
-
let launch;
|
|
314
|
-
try {
|
|
315
|
-
launch = await (0, import_chrome_launcher.launchStableChrome)({
|
|
316
|
-
chromePath: options.chromePath,
|
|
317
|
-
profilePath: requestedProfilePath,
|
|
318
|
-
playwrightDownloadsPath,
|
|
319
|
-
lockPath: (0, import_chrome_launcher.defaultChromeLockPath)(),
|
|
320
|
-
onLaunchAttemptFailed: (attempt, attempts, error) => {
|
|
321
|
-
process.stderr.write(
|
|
322
|
-
`[r5d-browser] Chrome launch attempt ${attempt}/${attempts} failed: ${error instanceof Error ? error.message : String(error)}
|
|
323
|
-
`
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
} catch (error) {
|
|
328
|
-
for (const [signal, handler] of signalHandlers) processEvents.off(signal, handler);
|
|
329
|
-
throw error;
|
|
330
|
-
}
|
|
331
|
-
if (requestedSignalOutcome) {
|
|
332
|
-
try {
|
|
333
|
-
await launch.close();
|
|
334
|
-
} finally {
|
|
335
|
-
for (const [signal, handler] of signalHandlers) processEvents.off(signal, handler);
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
try {
|
|
340
|
-
const profilePath = launch.profilePath;
|
|
341
|
-
const uploadsPath = import_node_path.default.join(browserRoot(), "uploads");
|
|
342
|
-
import_node_fs.default.rmSync(uploadsPath, { recursive: true, force: true });
|
|
343
|
-
const runtime = new import_browser_runtime.BrowserRuntime(launch.context, downloadsPath, uploadsPath);
|
|
344
|
-
const instanceId = loadInstanceId();
|
|
345
|
-
const portForwards = new import_port_forward_manager.BrowserPortForwardManager(baseUrl, token, instanceId);
|
|
346
|
-
process.stdout.write(`[r5d-browser] profile: ${profilePath}
|
|
347
|
-
`);
|
|
348
|
-
process.stdout.write(`[r5d-browser] downloads: ${downloadsPath}
|
|
349
|
-
`);
|
|
350
|
-
process.stdout.write(`[r5d-browser] Google Chrome ${launch.chromeVersion}; navigator.webdriver is disabled.
|
|
351
|
-
`);
|
|
352
|
-
process.stdout.write(`[r5d-browser] agents use synthetic Chrome input; your system pointer and keyboard stay untouched.
|
|
353
|
-
`);
|
|
354
|
-
const abortController = new AbortController();
|
|
355
|
-
const controlLoop = connectLoop({
|
|
356
|
-
runtime,
|
|
357
|
-
portForwards,
|
|
358
|
-
baseUrl,
|
|
359
|
-
token,
|
|
360
|
-
instanceId,
|
|
361
|
-
profilePath,
|
|
362
|
-
chromiumVersion: launch.chromeVersion,
|
|
363
|
-
signal: abortController.signal
|
|
364
|
-
});
|
|
365
|
-
const controlStopped = controlLoop.then(
|
|
366
|
-
() => ({ exitCode: abortController.signal.aborted ? 0 : 1, message: "The r5d.dev browser control loop stopped unexpectedly." }),
|
|
367
|
-
(error) => ({
|
|
368
|
-
exitCode: 1,
|
|
369
|
-
message: `The r5d.dev browser control loop failed: ${error instanceof Error ? error.message : String(error)}`
|
|
370
|
-
})
|
|
371
|
-
);
|
|
372
|
-
const chromeExited = launch.exited.then(({ code, signal }) => ({
|
|
373
|
-
exitCode: code === 0 ? 0 : 1,
|
|
374
|
-
message: code === 0 ? void 0 : `Google Chrome exited unexpectedly (${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`}).`
|
|
375
|
-
}));
|
|
376
|
-
const chromeDisconnected = launch.disconnected.then(async () => {
|
|
377
|
-
const exitOutcome = await resultWithin(chromeExited, CHROME_DISCONNECT_EXIT_GRACE_MS);
|
|
378
|
-
if (exitOutcome) return exitOutcome;
|
|
379
|
-
if (!isProcessAlive(launch.chromePid)) return await chromeExited;
|
|
380
|
-
return { exitCode: 1, message: "The Google Chrome control connection closed unexpectedly." };
|
|
381
|
-
});
|
|
382
|
-
const outcome = await Promise.race([requestedStop, chromeExited, chromeDisconnected, controlStopped]);
|
|
383
|
-
abortController.abort();
|
|
384
|
-
let cleanupError;
|
|
385
|
-
try {
|
|
386
|
-
await portForwards.close();
|
|
387
|
-
} catch (error) {
|
|
388
|
-
cleanupError = error;
|
|
389
|
-
}
|
|
390
|
-
try {
|
|
391
|
-
if (!await waitUpTo(runtime.drainDownloads(), 5e3)) {
|
|
392
|
-
process.stderr.write("[r5d-browser] timed out waiting for active downloads during shutdown.\n");
|
|
393
|
-
}
|
|
394
|
-
} catch (error) {
|
|
395
|
-
cleanupError ??= error;
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
await launch.close();
|
|
399
|
-
} catch (error) {
|
|
400
|
-
cleanupError ??= error;
|
|
401
|
-
}
|
|
402
|
-
runtime.closeUploads();
|
|
403
|
-
await controlStopped;
|
|
404
|
-
for (const [signal, handler] of signalHandlers) processEvents.off(signal, handler);
|
|
405
|
-
if (outcome.exitCode !== 0) throw new Error(outcome.message ?? "Google Chrome stopped unexpectedly.");
|
|
406
|
-
if (cleanupError) throw cleanupError;
|
|
407
|
-
} catch (error) {
|
|
408
|
-
try {
|
|
409
|
-
await launch.close().catch(() => void 0);
|
|
410
|
-
} finally {
|
|
411
|
-
for (const [signal, handler] of signalHandlers) processEvents.off(signal, handler);
|
|
412
|
-
}
|
|
413
|
-
throw error;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
async function main() {
|
|
417
|
-
const { command, options } = (0, import_cli_options.parseBrowserArgs)(process.argv.slice(2));
|
|
418
|
-
if (command === "help") return printHelp();
|
|
419
|
-
if (command === "version") return void process.stdout.write(`r5d-browser ${findVersion()}
|
|
420
|
-
`);
|
|
421
|
-
await start(options);
|
|
422
|
-
}
|
|
423
|
-
main().catch((error) => {
|
|
424
|
-
process.stderr.write(`r5d-browser: ${error instanceof Error ? error.message : String(error)}
|
|
425
|
-
`);
|
|
426
|
-
process.exit(1);
|
|
427
|
-
});
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var password_safe_snapshot_exports = {};
|
|
20
|
-
__export(password_safe_snapshot_exports, {
|
|
21
|
-
passwordSafeAriaSnapshot: () => passwordSafeAriaSnapshot
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(password_safe_snapshot_exports);
|
|
24
|
-
var import_node_module = require("node:module");
|
|
25
|
-
const import_meta = {};
|
|
26
|
-
const requirePlaywright = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url);
|
|
27
|
-
const SUPPORTED_PLAYWRIGHT_VERSION = "1.62.1";
|
|
28
|
-
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
29
|
-
const UNAVAILABLE = "Password-safe browser snapshots are unavailable with this Playwright runtime. Update r5d-browser before retrying.";
|
|
30
|
-
function isServerFrame(value) {
|
|
31
|
-
return !!value && typeof value.selectors?.callOnSelector === "function";
|
|
32
|
-
}
|
|
33
|
-
function captureInUtilityWorld({ elements, injected }, options) {
|
|
34
|
-
const root = elements[0];
|
|
35
|
-
const valueDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
|
|
36
|
-
const typeDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "type");
|
|
37
|
-
const attributeDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "getAttribute");
|
|
38
|
-
if (!root || typeof injected.ariaSnapshotWithRefs !== "function" || !valueDescriptor?.configurable || typeof valueDescriptor.get !== "function" || typeof typeDescriptor?.get !== "function" || !attributeDescriptor?.configurable || typeof attributeDescriptor.value !== "function") {
|
|
39
|
-
throw new Error("Password-safe snapshot utility APIs are unavailable.");
|
|
40
|
-
}
|
|
41
|
-
const readValue = valueDescriptor.get;
|
|
42
|
-
const readType = typeDescriptor.get;
|
|
43
|
-
const readAttribute = attributeDescriptor.value;
|
|
44
|
-
const isPassword = (element) => element instanceof HTMLInputElement && readType.call(element) === "password";
|
|
45
|
-
try {
|
|
46
|
-
Object.defineProperty(HTMLInputElement.prototype, "value", {
|
|
47
|
-
...valueDescriptor,
|
|
48
|
-
get() {
|
|
49
|
-
const value = readValue.call(this);
|
|
50
|
-
return value && isPassword(this) ? "[redacted]" : value;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
Object.defineProperty(Element.prototype, "getAttribute", {
|
|
54
|
-
...attributeDescriptor,
|
|
55
|
-
value(name) {
|
|
56
|
-
const value = readAttribute.call(this, name);
|
|
57
|
-
return value && String(name).toLowerCase() === "value" && isPassword(this) ? "[redacted]" : value;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
const snapshot = injected.ariaSnapshotWithRefs(root, options);
|
|
61
|
-
if (snapshot && typeof snapshot.then === "function") {
|
|
62
|
-
throw new Error("Password-safe snapshot generation must be synchronous.");
|
|
63
|
-
}
|
|
64
|
-
return snapshot;
|
|
65
|
-
} finally {
|
|
66
|
-
Object.defineProperty(Element.prototype, "getAttribute", attributeDescriptor);
|
|
67
|
-
Object.defineProperty(HTMLInputElement.prototype, "value", valueDescriptor);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function validateSnapshot(value) {
|
|
71
|
-
const snapshot = value;
|
|
72
|
-
if (!snapshot || typeof snapshot.text !== "string" || !Array.isArray(snapshot.iframeRefs) || !snapshot.iframeDepths || typeof snapshot.iframeDepths !== "object" || snapshot.iframeRefs.some((ref) => typeof ref !== "string" || !/^(f\d+)?e\d+$/.test(ref)) || Object.entries(snapshot.iframeDepths).some(([ref, depth]) => !/^(f\d+)?e\d+$/.test(ref) || !Number.isInteger(depth) || depth < 0)) {
|
|
73
|
-
throw new Error(UNAVAILABLE);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function passwordSafeAriaSnapshot(page, options = {}) {
|
|
77
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
78
|
-
if (!Number.isFinite(timeout) || timeout <= 0) throw new Error("Password-safe snapshots require a finite positive timeout.");
|
|
79
|
-
let frame;
|
|
80
|
-
try {
|
|
81
|
-
if (requirePlaywright("playwright-core/package.json").version !== SUPPORTED_PLAYWRIGHT_VERSION) throw new Error(UNAVAILABLE);
|
|
82
|
-
const connection = page._connection;
|
|
83
|
-
if (typeof connection?.toImpl !== "function") throw new Error(UNAVAILABLE);
|
|
84
|
-
frame = connection.toImpl(page.mainFrame());
|
|
85
|
-
} catch {
|
|
86
|
-
throw new Error(UNAVAILABLE);
|
|
87
|
-
}
|
|
88
|
-
if (!isServerFrame(frame)) throw new Error(UNAVAILABLE);
|
|
89
|
-
const deadline = Date.now() + timeout;
|
|
90
|
-
const renderOptions = {
|
|
91
|
-
mode: "ai",
|
|
92
|
-
...options.depth === void 0 ? {} : { depth: options.depth },
|
|
93
|
-
boxes: options.boxes
|
|
94
|
-
};
|
|
95
|
-
const timeoutError = () => new Error(`Timeout ${timeout}ms exceeded building a password-safe browser snapshot.`);
|
|
96
|
-
const bounded = async (operation) => {
|
|
97
|
-
let timer;
|
|
98
|
-
try {
|
|
99
|
-
return await Promise.race([
|
|
100
|
-
operation,
|
|
101
|
-
new Promise((_, reject) => {
|
|
102
|
-
timer = setTimeout(() => reject(timeoutError()), Math.max(0, deadline - Date.now()));
|
|
103
|
-
})
|
|
104
|
-
]);
|
|
105
|
-
} finally {
|
|
106
|
-
clearTimeout(timer);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const captureFrame = async (currentFrame, selector, frameOptions, isMainFrame = false) => {
|
|
110
|
-
if (!isServerFrame(currentFrame)) throw new Error(UNAVAILABLE);
|
|
111
|
-
if (Date.now() >= deadline) throw timeoutError();
|
|
112
|
-
const captured = await bounded(currentFrame.selectors.callOnSelector(selector, { strict: false }, captureInUtilityWorld, frameOptions));
|
|
113
|
-
if (!captured) {
|
|
114
|
-
if (!isMainFrame) return [];
|
|
115
|
-
await bounded(new Promise((resolve) => setTimeout(resolve, 50)));
|
|
116
|
-
return captureFrame(currentFrame, selector, frameOptions, true);
|
|
117
|
-
}
|
|
118
|
-
if (captured.info?.world !== "utility" || !isServerFrame(captured.frame)) throw new Error(UNAVAILABLE);
|
|
119
|
-
validateSnapshot(captured.result);
|
|
120
|
-
const snapshot = captured.result;
|
|
121
|
-
const renderedRefs = snapshot.iframeRefs.filter((ref) => Object.hasOwn(snapshot.iframeDepths, ref));
|
|
122
|
-
const children = /* @__PURE__ */ new Map();
|
|
123
|
-
await Promise.all(
|
|
124
|
-
renderedRefs.map(async (ref) => {
|
|
125
|
-
const childDepth = frameOptions.depth === void 0 ? void 0 : frameOptions.depth - snapshot.iframeDepths[ref] - 1;
|
|
126
|
-
if (childDepth !== void 0 && childDepth <= 0) return;
|
|
127
|
-
children.set(
|
|
128
|
-
ref,
|
|
129
|
-
await captureFrame(captured.frame, `aria-ref=${ref} >> internal:control=enter-frame >> body,frameset`, {
|
|
130
|
-
...frameOptions,
|
|
131
|
-
...childDepth === void 0 ? {} : { depth: childDepth }
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
})
|
|
135
|
-
);
|
|
136
|
-
const lines = [];
|
|
137
|
-
for (const line of snapshot.text.split("\n")) {
|
|
138
|
-
const match = line.match(/^(\s*)- iframe (?:\[active\] )?\[ref=([^\]]*)\]/);
|
|
139
|
-
const child = match ? children.get(match[2]) : void 0;
|
|
140
|
-
lines.push(child?.length ? `${line}:` : line);
|
|
141
|
-
if (match && child) lines.push(...child.map((childLine) => `${match[1]} ${childLine}`));
|
|
142
|
-
}
|
|
143
|
-
return lines;
|
|
144
|
-
};
|
|
145
|
-
return (await captureFrame(frame, "body,frameset", renderOptions, true)).join("\n");
|
|
146
|
-
}
|
|
147
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
148
|
-
0 && (module.exports = {
|
|
149
|
-
passwordSafeAriaSnapshot
|
|
150
|
-
});
|