@web-auto/camo 0.2.0 → 0.2.1
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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1255
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -127
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -671
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -304
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -222
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -302
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
import { isTimeoutLikeError } from './utils.js';
|
|
2
|
-
import { resolveInputMode } from './utils.js';
|
|
3
|
-
|
|
4
|
-
async function createCDPSession(page) {
|
|
5
|
-
const context = page.context();
|
|
6
|
-
return context.newCDPSession(page);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function cdpMouseClick(cdp, x, y, button = 'left', delay = 50) {
|
|
10
|
-
const normalizedButton = button === 'left' ? 'left' : button === 'right' ? 'right' : button === 'middle' ? 'middle' : 'left';
|
|
11
|
-
await cdp.send('Input.dispatchMouseEvent', {
|
|
12
|
-
type: 'mousePressed',
|
|
13
|
-
x: Math.round(x),
|
|
14
|
-
y: Math.round(y),
|
|
15
|
-
button: normalizedButton,
|
|
16
|
-
clickCount: 1
|
|
17
|
-
});
|
|
18
|
-
if (delay > 0) {
|
|
19
|
-
await new Promise(r => setTimeout(r, delay));
|
|
20
|
-
}
|
|
21
|
-
await cdp.send('Input.dispatchMouseEvent', {
|
|
22
|
-
type: 'mouseReleased',
|
|
23
|
-
x: Math.round(x),
|
|
24
|
-
y: Math.round(y),
|
|
25
|
-
button: normalizedButton,
|
|
26
|
-
clickCount: 1
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function cdpMouseMove(cdp, x, y) {
|
|
31
|
-
await cdp.send('Input.dispatchMouseEvent', {
|
|
32
|
-
type: 'mouseMoved',
|
|
33
|
-
x: Math.round(x),
|
|
34
|
-
y: Math.round(y)
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function readInteractiveViewport(page) {
|
|
39
|
-
const fallback = page.viewportSize?.() || null;
|
|
40
|
-
try {
|
|
41
|
-
const metrics = await page.evaluate(() => ({
|
|
42
|
-
innerWidth: Number(window.innerWidth || 0),
|
|
43
|
-
innerHeight: Number(window.innerHeight || 0),
|
|
44
|
-
visualWidth: Number(window.visualViewport?.width || 0),
|
|
45
|
-
visualHeight: Number(window.visualViewport?.height || 0),
|
|
46
|
-
}));
|
|
47
|
-
const width = Math.max(Number(metrics?.innerWidth || 0), Number(metrics?.visualWidth || 0), Number(fallback?.width || 0));
|
|
48
|
-
const height = Math.max(Number(metrics?.innerHeight || 0), Number(metrics?.visualHeight || 0), Number(fallback?.height || 0));
|
|
49
|
-
if (Number.isFinite(width) && width > 1 && Number.isFinite(height) && height > 1) {
|
|
50
|
-
return {
|
|
51
|
-
width: Math.round(width),
|
|
52
|
-
height: Math.round(height),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch { }
|
|
57
|
-
return {
|
|
58
|
-
width: Math.max(1, Number(fallback?.width || 1280)),
|
|
59
|
-
height: Math.max(1, Number(fallback?.height || 720)),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class BrowserSessionInputOps {
|
|
64
|
-
ensurePrimaryPage;
|
|
65
|
-
ensureInputReady;
|
|
66
|
-
runInputAction;
|
|
67
|
-
withInputActionLock;
|
|
68
|
-
wheelMode = 'wheel';
|
|
69
|
-
inputActionTail = Promise.resolve();
|
|
70
|
-
constructor(ensurePrimaryPage, ensureInputReady, runInputAction, withInputActionLock) {
|
|
71
|
-
this.ensurePrimaryPage = ensurePrimaryPage;
|
|
72
|
-
this.ensureInputReady = ensureInputReady;
|
|
73
|
-
this.runInputAction = runInputAction;
|
|
74
|
-
this.withInputActionLock = withInputActionLock;
|
|
75
|
-
const envMode = String(process.env.CAMO_SCROLL_INPUT_MODE || '').trim().toLowerCase();
|
|
76
|
-
this.wheelMode = envMode === 'keyboard' ? 'keyboard' : 'wheel';
|
|
77
|
-
this.inputMode = resolveInputMode();
|
|
78
|
-
}
|
|
79
|
-
async mouseClick(opts) {
|
|
80
|
-
const page = await this.ensurePrimaryPage();
|
|
81
|
-
|
|
82
|
-
if (this.inputMode === 'cdp') {
|
|
83
|
-
const { x, y, button = 'left', clicks = 1, delay = 50 } = opts;
|
|
84
|
-
let cdp = null;
|
|
85
|
-
try {
|
|
86
|
-
cdp = await createCDPSession(page);
|
|
87
|
-
for (let i = 0; i < clicks; i++) {
|
|
88
|
-
if (i > 0) {
|
|
89
|
-
await new Promise(r => setTimeout(r, 100 + Math.random() * 100));
|
|
90
|
-
}
|
|
91
|
-
await this.withInputActionLock(async () => {
|
|
92
|
-
await this.runInputAction(page, 'mouse:click(cdp)', async () => {
|
|
93
|
-
await cdpMouseClick(cdp, x, y, button, delay);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
} finally {
|
|
98
|
-
if (cdp) {
|
|
99
|
-
await cdp.detach().catch(() => {});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await this.withInputActionLock(async () => {
|
|
106
|
-
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
107
|
-
const { x, y, button = 'left', clicks = 1, delay = 50, nudgeBefore = false } = opts;
|
|
108
|
-
const moveToTarget = async (clickPage) => {
|
|
109
|
-
try {
|
|
110
|
-
await clickPage.mouse.move(x, y, { steps: 1 });
|
|
111
|
-
}
|
|
112
|
-
catch { }
|
|
113
|
-
};
|
|
114
|
-
const nudgePointer = async (clickPage) => {
|
|
115
|
-
const viewport = clickPage.viewportSize();
|
|
116
|
-
const maxX = Math.max(2, Number(viewport?.width || 1280) - 2);
|
|
117
|
-
const maxY = Math.max(2, Number(viewport?.height || 720) - 2);
|
|
118
|
-
const nudgeX = Math.max(2, Math.min(maxX, Math.round(Math.max(24, x * 0.2))));
|
|
119
|
-
const nudgeY = Math.max(2, Math.min(maxY, Math.round(Math.max(24, y * 0.2))));
|
|
120
|
-
await clickPage.mouse.move(nudgeX, nudgeY, { steps: 3 }).catch(() => { });
|
|
121
|
-
await clickPage.waitForTimeout(40).catch(() => { });
|
|
122
|
-
};
|
|
123
|
-
const performClick = async (clickPage, label) => {
|
|
124
|
-
await this.runInputAction(page, label, async (activePage) => {
|
|
125
|
-
if (nudgeBefore)
|
|
126
|
-
await nudgePointer(activePage);
|
|
127
|
-
await moveToTarget(activePage);
|
|
128
|
-
await activePage.mouse.down({ button });
|
|
129
|
-
const pause = Math.max(0, Number(delay) || 0);
|
|
130
|
-
if (pause > 0)
|
|
131
|
-
await activePage.waitForTimeout(pause).catch(() => { });
|
|
132
|
-
await activePage.mouse.up({ button });
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
for (let i = 0; i < clicks; i++) {
|
|
136
|
-
if (i > 0)
|
|
137
|
-
await new Promise(r => setTimeout(r, 100 + Math.random() * 100));
|
|
138
|
-
try {
|
|
139
|
-
await performClick(page, 'mouse:click(direct)');
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
if (!isTimeoutLikeError(error))
|
|
143
|
-
throw error;
|
|
144
|
-
await performClick(page, 'mouse:click(retry)');
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
async mouseMove(opts) {
|
|
150
|
-
const x = Number(opts?.x);
|
|
151
|
-
const y = Number(opts?.y);
|
|
152
|
-
throw new Error(`mouse:move disabled (x=${Number.isFinite(x) ? x : 'NaN'}, y=${Number.isFinite(y) ? y : 'NaN'})`);
|
|
153
|
-
}
|
|
154
|
-
async mouseWheel(opts) {
|
|
155
|
-
const page = await this.ensurePrimaryPage();
|
|
156
|
-
await this.withInputActionLock(async () => {
|
|
157
|
-
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
158
|
-
const { deltaX = 0, deltaY, anchorX, anchorY } = opts;
|
|
159
|
-
const normalizedDeltaX = Number(deltaX) || 0;
|
|
160
|
-
const normalizedDeltaY = Number(deltaY) || 0;
|
|
161
|
-
const normalizedAnchorX = Number(anchorX);
|
|
162
|
-
const normalizedAnchorY = Number(anchorY);
|
|
163
|
-
if (normalizedDeltaY === 0 && normalizedDeltaX === 0)
|
|
164
|
-
return;
|
|
165
|
-
const keyboardKey = normalizedDeltaY > 0 ? 'PageDown' : 'PageUp';
|
|
166
|
-
const keyboardTimes = Math.max(1, Math.min(4, Math.round(Math.abs(normalizedDeltaY) / 420) || 1));
|
|
167
|
-
const runKeyboardWheel = async () => {
|
|
168
|
-
for (let i = 0; i < keyboardTimes; i += 1) {
|
|
169
|
-
await this.runInputAction(page, `mouse:wheel:keyboard:${keyboardKey}`, (p) => p.keyboard.press(keyboardKey));
|
|
170
|
-
if (i + 1 < keyboardTimes) {
|
|
171
|
-
await this.runInputAction(page, 'mouse:wheel:keyboard:wait', (p) => p.waitForTimeout(80));
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
if (this.wheelMode === 'keyboard') {
|
|
176
|
-
await runKeyboardWheel();
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
await this.runInputAction(page, 'mouse:wheel', async (activePage) => {
|
|
181
|
-
const viewport = await readInteractiveViewport(activePage);
|
|
182
|
-
const moveX = Number.isFinite(normalizedAnchorX)
|
|
183
|
-
? Math.max(1, Math.min(Math.max(1, Number(viewport?.width || 1280) - 1), Math.round(normalizedAnchorX)))
|
|
184
|
-
: Math.max(1, Math.floor(((viewport?.width || 1280) * 0.5)));
|
|
185
|
-
const moveY = Number.isFinite(normalizedAnchorY)
|
|
186
|
-
? Math.max(1, Math.min(Math.max(1, Number(viewport?.height || 720) - 1), Math.round(normalizedAnchorY)))
|
|
187
|
-
: Math.max(1, Math.floor(((viewport?.height || 720) * 0.5)));
|
|
188
|
-
await activePage.mouse.move(moveX, moveY, { steps: 1 }).catch(() => { });
|
|
189
|
-
await activePage.mouse.wheel(normalizedDeltaX, normalizedDeltaY);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
if (!isTimeoutLikeError(error) || normalizedDeltaX !== 0 || normalizedDeltaY === 0)
|
|
194
|
-
throw error;
|
|
195
|
-
this.wheelMode = 'keyboard';
|
|
196
|
-
await runKeyboardWheel();
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
async keyboardType(opts) {
|
|
201
|
-
const page = await this.ensurePrimaryPage();
|
|
202
|
-
await this.withInputActionLock(async () => {
|
|
203
|
-
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
204
|
-
const { text, delay = 80, submit } = opts;
|
|
205
|
-
if (text && text.length > 0) {
|
|
206
|
-
await this.runInputAction(page, 'keyboard:type', (activePage) => activePage.keyboard.type(text, { delay }));
|
|
207
|
-
}
|
|
208
|
-
if (submit) {
|
|
209
|
-
await this.runInputAction(page, 'keyboard:press', (activePage) => activePage.keyboard.press('Enter'));
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
async keyboardPress(opts) {
|
|
214
|
-
const page = await this.ensurePrimaryPage();
|
|
215
|
-
await this.withInputActionLock(async () => {
|
|
216
|
-
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
217
|
-
const { key, delay } = opts;
|
|
218
|
-
await this.runInputAction(page, 'keyboard:press', (activePage) => activePage.keyboard.press(key, typeof delay === 'number' ? { delay } : undefined));
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
//# sourceMappingURL=input-ops.js.map
|
|
1
|
+
import { isTimeoutLikeError } from './utils.js';
|
|
2
|
+
import { resolveInputMode } from './utils.js';
|
|
3
|
+
|
|
4
|
+
async function createCDPSession(page) {
|
|
5
|
+
const context = page.context();
|
|
6
|
+
return context.newCDPSession(page);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function cdpMouseClick(cdp, x, y, button = 'left', delay = 50) {
|
|
10
|
+
const normalizedButton = button === 'left' ? 'left' : button === 'right' ? 'right' : button === 'middle' ? 'middle' : 'left';
|
|
11
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
12
|
+
type: 'mousePressed',
|
|
13
|
+
x: Math.round(x),
|
|
14
|
+
y: Math.round(y),
|
|
15
|
+
button: normalizedButton,
|
|
16
|
+
clickCount: 1
|
|
17
|
+
});
|
|
18
|
+
if (delay > 0) {
|
|
19
|
+
await new Promise(r => setTimeout(r, delay));
|
|
20
|
+
}
|
|
21
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
22
|
+
type: 'mouseReleased',
|
|
23
|
+
x: Math.round(x),
|
|
24
|
+
y: Math.round(y),
|
|
25
|
+
button: normalizedButton,
|
|
26
|
+
clickCount: 1
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function cdpMouseMove(cdp, x, y) {
|
|
31
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
32
|
+
type: 'mouseMoved',
|
|
33
|
+
x: Math.round(x),
|
|
34
|
+
y: Math.round(y)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readInteractiveViewport(page) {
|
|
39
|
+
const fallback = page.viewportSize?.() || null;
|
|
40
|
+
try {
|
|
41
|
+
const metrics = await page.evaluate(() => ({
|
|
42
|
+
innerWidth: Number(window.innerWidth || 0),
|
|
43
|
+
innerHeight: Number(window.innerHeight || 0),
|
|
44
|
+
visualWidth: Number(window.visualViewport?.width || 0),
|
|
45
|
+
visualHeight: Number(window.visualViewport?.height || 0),
|
|
46
|
+
}));
|
|
47
|
+
const width = Math.max(Number(metrics?.innerWidth || 0), Number(metrics?.visualWidth || 0), Number(fallback?.width || 0));
|
|
48
|
+
const height = Math.max(Number(metrics?.innerHeight || 0), Number(metrics?.visualHeight || 0), Number(fallback?.height || 0));
|
|
49
|
+
if (Number.isFinite(width) && width > 1 && Number.isFinite(height) && height > 1) {
|
|
50
|
+
return {
|
|
51
|
+
width: Math.round(width),
|
|
52
|
+
height: Math.round(height),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
return {
|
|
58
|
+
width: Math.max(1, Number(fallback?.width || 1280)),
|
|
59
|
+
height: Math.max(1, Number(fallback?.height || 720)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class BrowserSessionInputOps {
|
|
64
|
+
ensurePrimaryPage;
|
|
65
|
+
ensureInputReady;
|
|
66
|
+
runInputAction;
|
|
67
|
+
withInputActionLock;
|
|
68
|
+
wheelMode = 'wheel';
|
|
69
|
+
inputActionTail = Promise.resolve();
|
|
70
|
+
constructor(ensurePrimaryPage, ensureInputReady, runInputAction, withInputActionLock) {
|
|
71
|
+
this.ensurePrimaryPage = ensurePrimaryPage;
|
|
72
|
+
this.ensureInputReady = ensureInputReady;
|
|
73
|
+
this.runInputAction = runInputAction;
|
|
74
|
+
this.withInputActionLock = withInputActionLock;
|
|
75
|
+
const envMode = String(process.env.CAMO_SCROLL_INPUT_MODE || '').trim().toLowerCase();
|
|
76
|
+
this.wheelMode = envMode === 'keyboard' ? 'keyboard' : 'wheel';
|
|
77
|
+
this.inputMode = resolveInputMode();
|
|
78
|
+
}
|
|
79
|
+
async mouseClick(opts) {
|
|
80
|
+
const page = await this.ensurePrimaryPage();
|
|
81
|
+
|
|
82
|
+
if (this.inputMode === 'cdp') {
|
|
83
|
+
const { x, y, button = 'left', clicks = 1, delay = 50 } = opts;
|
|
84
|
+
let cdp = null;
|
|
85
|
+
try {
|
|
86
|
+
cdp = await createCDPSession(page);
|
|
87
|
+
for (let i = 0; i < clicks; i++) {
|
|
88
|
+
if (i > 0) {
|
|
89
|
+
await new Promise(r => setTimeout(r, 100 + Math.random() * 100));
|
|
90
|
+
}
|
|
91
|
+
await this.withInputActionLock(async () => {
|
|
92
|
+
await this.runInputAction(page, 'mouse:click(cdp)', async () => {
|
|
93
|
+
await cdpMouseClick(cdp, x, y, button, delay);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
if (cdp) {
|
|
99
|
+
await cdp.detach().catch(() => {});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await this.withInputActionLock(async () => {
|
|
106
|
+
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
107
|
+
const { x, y, button = 'left', clicks = 1, delay = 50, nudgeBefore = false } = opts;
|
|
108
|
+
const moveToTarget = async (clickPage) => {
|
|
109
|
+
try {
|
|
110
|
+
await clickPage.mouse.move(x, y, { steps: 1 });
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
};
|
|
114
|
+
const nudgePointer = async (clickPage) => {
|
|
115
|
+
const viewport = clickPage.viewportSize();
|
|
116
|
+
const maxX = Math.max(2, Number(viewport?.width || 1280) - 2);
|
|
117
|
+
const maxY = Math.max(2, Number(viewport?.height || 720) - 2);
|
|
118
|
+
const nudgeX = Math.max(2, Math.min(maxX, Math.round(Math.max(24, x * 0.2))));
|
|
119
|
+
const nudgeY = Math.max(2, Math.min(maxY, Math.round(Math.max(24, y * 0.2))));
|
|
120
|
+
await clickPage.mouse.move(nudgeX, nudgeY, { steps: 3 }).catch(() => { });
|
|
121
|
+
await clickPage.waitForTimeout(40).catch(() => { });
|
|
122
|
+
};
|
|
123
|
+
const performClick = async (clickPage, label) => {
|
|
124
|
+
await this.runInputAction(page, label, async (activePage) => {
|
|
125
|
+
if (nudgeBefore)
|
|
126
|
+
await nudgePointer(activePage);
|
|
127
|
+
await moveToTarget(activePage);
|
|
128
|
+
await activePage.mouse.down({ button });
|
|
129
|
+
const pause = Math.max(0, Number(delay) || 0);
|
|
130
|
+
if (pause > 0)
|
|
131
|
+
await activePage.waitForTimeout(pause).catch(() => { });
|
|
132
|
+
await activePage.mouse.up({ button });
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
for (let i = 0; i < clicks; i++) {
|
|
136
|
+
if (i > 0)
|
|
137
|
+
await new Promise(r => setTimeout(r, 100 + Math.random() * 100));
|
|
138
|
+
try {
|
|
139
|
+
await performClick(page, 'mouse:click(direct)');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!isTimeoutLikeError(error))
|
|
143
|
+
throw error;
|
|
144
|
+
await performClick(page, 'mouse:click(retry)');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async mouseMove(opts) {
|
|
150
|
+
const x = Number(opts?.x);
|
|
151
|
+
const y = Number(opts?.y);
|
|
152
|
+
throw new Error(`mouse:move disabled (x=${Number.isFinite(x) ? x : 'NaN'}, y=${Number.isFinite(y) ? y : 'NaN'})`);
|
|
153
|
+
}
|
|
154
|
+
async mouseWheel(opts) {
|
|
155
|
+
const page = await this.ensurePrimaryPage();
|
|
156
|
+
await this.withInputActionLock(async () => {
|
|
157
|
+
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
158
|
+
const { deltaX = 0, deltaY, anchorX, anchorY } = opts;
|
|
159
|
+
const normalizedDeltaX = Number(deltaX) || 0;
|
|
160
|
+
const normalizedDeltaY = Number(deltaY) || 0;
|
|
161
|
+
const normalizedAnchorX = Number(anchorX);
|
|
162
|
+
const normalizedAnchorY = Number(anchorY);
|
|
163
|
+
if (normalizedDeltaY === 0 && normalizedDeltaX === 0)
|
|
164
|
+
return;
|
|
165
|
+
const keyboardKey = normalizedDeltaY > 0 ? 'PageDown' : 'PageUp';
|
|
166
|
+
const keyboardTimes = Math.max(1, Math.min(4, Math.round(Math.abs(normalizedDeltaY) / 420) || 1));
|
|
167
|
+
const runKeyboardWheel = async () => {
|
|
168
|
+
for (let i = 0; i < keyboardTimes; i += 1) {
|
|
169
|
+
await this.runInputAction(page, `mouse:wheel:keyboard:${keyboardKey}`, (p) => p.keyboard.press(keyboardKey));
|
|
170
|
+
if (i + 1 < keyboardTimes) {
|
|
171
|
+
await this.runInputAction(page, 'mouse:wheel:keyboard:wait', (p) => p.waitForTimeout(80));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
if (this.wheelMode === 'keyboard') {
|
|
176
|
+
await runKeyboardWheel();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
await this.runInputAction(page, 'mouse:wheel', async (activePage) => {
|
|
181
|
+
const viewport = await readInteractiveViewport(activePage);
|
|
182
|
+
const moveX = Number.isFinite(normalizedAnchorX)
|
|
183
|
+
? Math.max(1, Math.min(Math.max(1, Number(viewport?.width || 1280) - 1), Math.round(normalizedAnchorX)))
|
|
184
|
+
: Math.max(1, Math.floor(((viewport?.width || 1280) * 0.5)));
|
|
185
|
+
const moveY = Number.isFinite(normalizedAnchorY)
|
|
186
|
+
? Math.max(1, Math.min(Math.max(1, Number(viewport?.height || 720) - 1), Math.round(normalizedAnchorY)))
|
|
187
|
+
: Math.max(1, Math.floor(((viewport?.height || 720) * 0.5)));
|
|
188
|
+
await activePage.mouse.move(moveX, moveY, { steps: 1 }).catch(() => { });
|
|
189
|
+
await activePage.mouse.wheel(normalizedDeltaX, normalizedDeltaY);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (!isTimeoutLikeError(error) || normalizedDeltaX !== 0 || normalizedDeltaY === 0)
|
|
194
|
+
throw error;
|
|
195
|
+
this.wheelMode = 'keyboard';
|
|
196
|
+
await runKeyboardWheel();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async keyboardType(opts) {
|
|
201
|
+
const page = await this.ensurePrimaryPage();
|
|
202
|
+
await this.withInputActionLock(async () => {
|
|
203
|
+
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
204
|
+
const { text, delay = 80, submit } = opts;
|
|
205
|
+
if (text && text.length > 0) {
|
|
206
|
+
await this.runInputAction(page, 'keyboard:type', (activePage) => activePage.keyboard.type(text, { delay }));
|
|
207
|
+
}
|
|
208
|
+
if (submit) {
|
|
209
|
+
await this.runInputAction(page, 'keyboard:press', (activePage) => activePage.keyboard.press('Enter'));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async keyboardPress(opts) {
|
|
214
|
+
const page = await this.ensurePrimaryPage();
|
|
215
|
+
await this.withInputActionLock(async () => {
|
|
216
|
+
await this.runInputAction(page, 'input:ready', (activePage) => this.ensureInputReady(activePage));
|
|
217
|
+
const { key, delay } = opts;
|
|
218
|
+
await this.runInputAction(page, 'keyboard:press', (activePage) => activePage.keyboard.press(key, typeof delay === 'number' ? { delay } : undefined));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=input-ops.js.map
|