h17-webpilot 0.1.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.
@@ -0,0 +1,32 @@
1
+ const { BridgeElement } = require('./element');
2
+
3
+
4
+ // BridgeCursor — ghost-cursor compatible cursor (raw, robotic movement)
5
+
6
+
7
+ class BridgeCursor {
8
+ constructor(page) {
9
+ this._page = page;
10
+ }
11
+
12
+ async moveTo(element) {
13
+ if (element instanceof BridgeElement) {
14
+ return this._page._send('dom.mouseMoveTo', { handleId: element._handleId });
15
+ }
16
+ return this._page._send('dom.mouseMoveTo', { selector: element });
17
+ }
18
+
19
+ async click(element) {
20
+ await this.moveTo(element);
21
+ if (element instanceof BridgeElement) {
22
+ return this._page._send('dom.click', { handleId: element._handleId });
23
+ }
24
+ return this._page._send('dom.click', { selector: element });
25
+ }
26
+ }
27
+
28
+ function createHumanCursor(page) {
29
+ return new BridgeCursor(page);
30
+ }
31
+
32
+ module.exports = { BridgeCursor, createHumanCursor };
@@ -0,0 +1,119 @@
1
+
2
+ // BridgeJSHandle — Puppeteer-compatible JSHandle
3
+
4
+
5
+ class BridgeJSHandle {
6
+ constructor(page, descriptor) {
7
+ this._page = page;
8
+ this._descriptor = descriptor; // { type, handleId, value, properties }
9
+ }
10
+
11
+ asElement() {
12
+ if (this._descriptor.type === 'element' && this._descriptor.handleId) {
13
+ return new BridgeElement(this._page, this._descriptor.handleId, '');
14
+ }
15
+ return null;
16
+ }
17
+
18
+ async jsonValue() {
19
+ if (this._descriptor.type === 'value') return this._descriptor.value;
20
+ if (this._descriptor.type === 'null') return null;
21
+ if (this._descriptor.type === 'object') {
22
+ const result = {};
23
+ for (const [key, prop] of Object.entries(this._descriptor.properties || {})) {
24
+ if (prop.type === 'value') result[key] = prop.value;
25
+ else result[key] = null;
26
+ }
27
+ return result;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ async getProperty(name) {
33
+ if (this._descriptor.type === 'object' && this._descriptor.properties) {
34
+ const prop = this._descriptor.properties[name];
35
+ if (prop) return new BridgeJSHandle(this._page, prop);
36
+ }
37
+ return new BridgeJSHandle(this._page, { type: 'null' });
38
+ }
39
+ }
40
+
41
+
42
+ // BridgeElement — Puppeteer-compatible ElementHandle
43
+
44
+
45
+ class BridgeElement {
46
+ constructor(page, handleId, selector) {
47
+ this._page = page;
48
+ this._handleId = handleId;
49
+ this._selector = selector;
50
+ }
51
+
52
+ asElement() {
53
+ return this;
54
+ }
55
+
56
+ async evaluate(fn, ...args) {
57
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
58
+ return this._page._send('dom.elementEvaluate', {
59
+ handleId: this._handleId, fn: fnString, args,
60
+ });
61
+ }
62
+
63
+ async boundingBox() {
64
+ return this._page._send('dom.boundingBox', { handleId: this._handleId });
65
+ }
66
+
67
+ async click(options = {}) {
68
+ return this._page._send('dom.click', { handleId: this._handleId, clickCount: options.clickCount });
69
+ }
70
+
71
+ async focus() {
72
+ return this._page._send('dom.focus', { handleId: this._handleId });
73
+ }
74
+
75
+ async type(text) {
76
+ await this.focus();
77
+ return this._page._send('dom.type', { text });
78
+ }
79
+
80
+ async $(selector) {
81
+ const handleId = await this._page._send('dom.querySelectorWithin', {
82
+ parentHandleId: this._handleId, selector,
83
+ });
84
+ if (!handleId) return null;
85
+ return new BridgeElement(this._page, handleId, selector);
86
+ }
87
+
88
+ async $$(selector) {
89
+ const handleIds = await this._page._send('dom.querySelectorAllWithin', {
90
+ parentHandleId: this._handleId, selector,
91
+ });
92
+ return handleIds.map(id => new BridgeElement(this._page, id, selector));
93
+ }
94
+
95
+ async getAttribute(name) {
96
+ return this._page._send('dom.getAttribute', { handleId: this._handleId, name });
97
+ }
98
+
99
+ async getProperty(name) {
100
+ return this._page._send('dom.getProperty', { handleId: this._handleId, name });
101
+ }
102
+
103
+ async isIntersectingViewport() {
104
+ const box = await this.boundingBox();
105
+ if (!box) return false;
106
+ const vp = await this._page.evaluate(() => ({
107
+ width: window.innerWidth,
108
+ height: window.innerHeight,
109
+ }));
110
+ return box.x + box.width > 0 && box.x < vp.width &&
111
+ box.y + box.height > 0 && box.y < vp.height;
112
+ }
113
+
114
+ async contentFrame() {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ module.exports = { BridgeElement, BridgeJSHandle };
@@ -0,0 +1,13 @@
1
+ const { BridgePage } = require('./page');
2
+ const { BridgeElement, BridgeJSHandle } = require('./element');
3
+ const { BridgeKeyboard } = require('./keyboard');
4
+ const { BridgeCursor, createHumanCursor } = require('./cursor');
5
+
6
+ module.exports = {
7
+ BridgePage,
8
+ BridgeElement,
9
+ BridgeJSHandle,
10
+ BridgeKeyboard,
11
+ BridgeCursor,
12
+ createHumanCursor,
13
+ };
@@ -0,0 +1,27 @@
1
+
2
+ // BridgeKeyboard — Puppeteer-compatible Keyboard
3
+
4
+
5
+ class BridgeKeyboard {
6
+ constructor(page) {
7
+ this._page = page;
8
+ }
9
+
10
+ async type(text) {
11
+ return this._page._send('dom.type', { text });
12
+ }
13
+
14
+ async press(key) {
15
+ return this._page._send('dom.keyPress', { key });
16
+ }
17
+
18
+ async down(key) {
19
+ return this._page._send('dom.keyDown', { key });
20
+ }
21
+
22
+ async up(key) {
23
+ return this._page._send('dom.keyUp', { key });
24
+ }
25
+ }
26
+
27
+ module.exports = { BridgeKeyboard };
package/client/page.js ADDED
@@ -0,0 +1,271 @@
1
+ const { BridgeElement, BridgeJSHandle } = require("./element");
2
+ const { BridgeKeyboard } = require("./keyboard");
3
+
4
+ // BridgePage — Puppeteer-compatible Page + human.* convenience methods
5
+
6
+ class BridgePage {
7
+ constructor(transport) {
8
+ this._transport = transport;
9
+ this._tabId = null;
10
+ this._currentUrl = "";
11
+ this.keyboard = new BridgeKeyboard(this);
12
+ this._frameworkConfig = {};
13
+
14
+ // Forward events from transport
15
+ transport.on("urlChanged", (data) => {
16
+ if (data.tabId === this._tabId && data.url) {
17
+ this._currentUrl = data.url;
18
+ }
19
+ });
20
+ }
21
+
22
+ _send(action, params = {}) {
23
+ return this._transport.send(action, params, this._tabId);
24
+ }
25
+
26
+ setTabId(tabId) {
27
+ this._tabId = tabId;
28
+ }
29
+
30
+ // --- Navigation ---
31
+
32
+ async goto(url, options = {}) {
33
+ await this._send("tabs.navigate", { url });
34
+ const tabs = await this._send("tabs.list");
35
+ const tab = tabs.find((t) => t.id === this._tabId);
36
+ this._currentUrl = tab ? tab.url : url;
37
+ }
38
+
39
+ async reload() {
40
+ await this._send("tabs.reload");
41
+ }
42
+
43
+ url() {
44
+ return this._currentUrl;
45
+ }
46
+
47
+ async title() {
48
+ // Try evaluate first, fall back to content script query
49
+ const result = await this.evaluate(() => document.title);
50
+ if (result !== null) return result;
51
+ // Fallback: query via DOM command
52
+ const handle = await this.$("title");
53
+ if (handle) {
54
+ const text = await this._send("dom.getProperty", {
55
+ handleId: handle._handleId,
56
+ property: "textContent",
57
+ });
58
+ return text;
59
+ }
60
+ return "";
61
+ }
62
+
63
+ async content() {
64
+ // Use CSP-safe dom.getHTML instead of evaluate
65
+ const result = await this._send("dom.getHTML");
66
+ return result?.html || "";
67
+ }
68
+
69
+ // Discover all interactive elements on the page (CSP-safe, no evaluate needed)
70
+ async discoverElements() {
71
+ return this._send("dom.discoverElements");
72
+ }
73
+
74
+ async batchQuery(selectors) {
75
+ if (!Array.isArray(selectors))
76
+ throw new Error("selectors must be an array");
77
+ return this._send("dom.batchQuery", { selectors });
78
+ }
79
+
80
+ // --- Screenshots ---
81
+
82
+ async screenshot(options = {}) {
83
+ const { path: filePath, fullPage = false } = options;
84
+ const { dataUrl } = await this._send("tabs.screenshot", { fullPage });
85
+ if (filePath) {
86
+ const fs = require("fs");
87
+ const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
88
+ fs.writeFileSync(filePath, Buffer.from(base64, "base64"));
89
+ }
90
+ return dataUrl;
91
+ }
92
+
93
+ // --- JavaScript Evaluation ---
94
+
95
+ async evaluate(fn, ...args) {
96
+ const fnString = typeof fn === "function" ? fn.toString() : fn;
97
+ return this._send("dom.evaluate", { fn: fnString, args });
98
+ }
99
+
100
+ async evaluateHandle(fn, ...args) {
101
+ const fnString = typeof fn === "function" ? fn.toString() : fn;
102
+
103
+ const elementMarkers = [];
104
+ const cleanArgs = [];
105
+ for (let i = 0; i < args.length; i++) {
106
+ if (args[i] instanceof BridgeElement) {
107
+ const markerId =
108
+ "ea_" + Date.now() + "_" + i + "_" + Math.floor(Math.random() * 1e6);
109
+ await this._send("dom.markElement", {
110
+ handleId: args[i]._handleId,
111
+ markerId,
112
+ });
113
+ elementMarkers.push({ index: i, markerId });
114
+ cleanArgs.push(null);
115
+ } else {
116
+ cleanArgs.push(args[i]);
117
+ }
118
+ }
119
+
120
+ const descriptor = await this._send("dom.evaluateHandle", {
121
+ fn: fnString,
122
+ args: cleanArgs,
123
+ elementMarkers,
124
+ });
125
+ return new BridgeJSHandle(this, descriptor);
126
+ }
127
+
128
+ async setConfig(config) {
129
+ const result = await this._send("framework.setConfig", { config });
130
+ if (result && result.framework) {
131
+ this._frameworkConfig = result.framework;
132
+ }
133
+ return result;
134
+ }
135
+
136
+ async getConfig() {
137
+ const result = await this._send("framework.getConfig");
138
+ if (result && result.framework) {
139
+ this._frameworkConfig = result.framework;
140
+ }
141
+ return result;
142
+ }
143
+
144
+ async waitForFunction(fn, options = {}, ...args) {
145
+ const { timeout = 30000 } = options;
146
+ const start = Date.now();
147
+ while (Date.now() - start < timeout) {
148
+ try {
149
+ const result = await this.evaluate(fn, ...args);
150
+ if (result) return result;
151
+ } catch {}
152
+ // Performance optimization: configurable or minimal polling for passive checks
153
+ const jitter =
154
+ this._frameworkConfig.pollInterval ||
155
+ (options.fast ? 50 : 150 + Math.floor(Math.random() * 200));
156
+ await new Promise((r) => setTimeout(r, jitter));
157
+ }
158
+ throw new Error(`waitForFunction timed out (${timeout}ms)`);
159
+ }
160
+
161
+ // --- DOM Queries ---
162
+
163
+ async $(selector) {
164
+ const handleId = await this._send("dom.querySelector", { selector });
165
+ if (!handleId) return null;
166
+ return new BridgeElement(this, handleId, selector);
167
+ }
168
+
169
+ async $$(selector) {
170
+ const handleIds = await this._send("dom.querySelectorAll", { selector });
171
+ return handleIds.map((id) => new BridgeElement(this, id, selector));
172
+ }
173
+
174
+ async waitForSelector(selector, options = {}) {
175
+ const { timeout = 120000 } = options;
176
+ const handleId = await this._send("dom.waitForSelector", {
177
+ selector,
178
+ timeout,
179
+ });
180
+ if (!handleId) return null;
181
+ return new BridgeElement(this, handleId, selector);
182
+ }
183
+
184
+ // --- Tab Management ---
185
+
186
+ async tabs() {
187
+ return this._send("tabs.list");
188
+ }
189
+
190
+ async close() {
191
+ return this._send("tabs.close");
192
+ }
193
+
194
+ async reload(options = {}) {
195
+ await this._send("tabs.reload");
196
+ const tabs = await this._send("tabs.list");
197
+ const tab = tabs.find((t) => t.id === this._tabId);
198
+ this._currentUrl = tab ? tab.url : this._currentUrl;
199
+ }
200
+
201
+ async waitForNavigation(options = {}) {
202
+ const result = await this._send("tabs.waitForNavigation", {
203
+ timeout: options.timeout || 30000,
204
+ });
205
+ const tabs = await this._send("tabs.list");
206
+ const tab = tabs.find((t) => t.id === this._tabId);
207
+ if (tab) this._currentUrl = tab.url;
208
+ return result;
209
+ }
210
+
211
+ async setViewport(viewport) {
212
+ // No-op: viewport is set via --window-size launch args
213
+ }
214
+
215
+ // --- Cookies ---
216
+
217
+ async cookies() {
218
+ return this._send("cookies.getAll", {});
219
+ }
220
+
221
+ async setCookie(...cookies) {
222
+ for (const cookie of cookies) {
223
+ await this._send("cookies.set", { cookie });
224
+ }
225
+ }
226
+
227
+ // --- Events ---
228
+
229
+ on(event, fn) {
230
+ if (event === "response") {
231
+ this._transport.on("response", (data) => {
232
+ if (this._tabId && data.tabId !== this._tabId) return;
233
+ fn({
234
+ url: () => data.url,
235
+ status: () => data.status,
236
+ method: () => data.method,
237
+ });
238
+ });
239
+ } else {
240
+ this._transport.on(event, fn);
241
+ }
242
+ }
243
+
244
+ // --- Human Commands (safe, human-like) ---
245
+
246
+ async humanClick(selectorOrElement, options = {}) {
247
+ const params =
248
+ selectorOrElement instanceof BridgeElement
249
+ ? { handleId: selectorOrElement._handleId }
250
+ : { selector: selectorOrElement };
251
+ return this._send("human.click", { ...params, ...options });
252
+ }
253
+
254
+ async humanType(text, options = {}) {
255
+ return this._send("human.type", { text, ...options });
256
+ }
257
+
258
+ async humanScroll(selector, options = {}) {
259
+ return this._send("human.scroll", { selector, ...options });
260
+ }
261
+
262
+ async humanClearInput(selectorOrElement, options = {}) {
263
+ const params =
264
+ selectorOrElement instanceof BridgeElement
265
+ ? { handleId: selectorOrElement._handleId }
266
+ : { selector: selectorOrElement };
267
+ return this._send("human.clearInput", { ...params, ...options });
268
+ }
269
+ }
270
+
271
+ module.exports = { BridgePage };