@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,189 +1,189 @@
|
|
|
1
|
-
import { callAPI } from '../../../utils/browser-service.mjs';
|
|
2
|
-
import { asErrorPayload, getCurrentUrl, normalizeArray } from '../utils.mjs';
|
|
3
|
-
|
|
4
|
-
function callWithTimeout(action, payload, timeoutMs) {
|
|
5
|
-
let timer = null;
|
|
6
|
-
return Promise.race([
|
|
7
|
-
callAPI(action, payload),
|
|
8
|
-
new Promise((_, reject) => {
|
|
9
|
-
timer = setTimeout(() => {
|
|
10
|
-
reject(new Error(`${action} timeout after ${timeoutMs}ms`));
|
|
11
|
-
}, timeoutMs);
|
|
12
|
-
}),
|
|
13
|
-
]).finally(() => {
|
|
14
|
-
if (timer) clearTimeout(timer);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function executeViewportOperation({ profileId, action, params = {} }) {
|
|
19
|
-
if (action === 'get_current_url') {
|
|
20
|
-
const url = await getCurrentUrl(profileId);
|
|
21
|
-
const includes = normalizeArray(params.includes || params.urlIncludes).map((item) => String(item));
|
|
22
|
-
const excludes = normalizeArray(params.excludes || params.urlExcludes).map((item) => String(item));
|
|
23
|
-
const missing = includes.filter((token) => !url.includes(token));
|
|
24
|
-
const forbidden = excludes.filter((token) => url.includes(token));
|
|
25
|
-
if (missing.length > 0 || forbidden.length > 0) {
|
|
26
|
-
return asErrorPayload('URL_MISMATCH', 'current url validation failed', {
|
|
27
|
-
url,
|
|
28
|
-
includes,
|
|
29
|
-
excludes,
|
|
30
|
-
missing,
|
|
31
|
-
forbidden,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return { ok: true, code: 'OPERATION_DONE', message: 'get_current_url done', data: { url } };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (action === 'sync_window_viewport') {
|
|
38
|
-
const rawWidth = Number(params.width ?? params.viewportWidth);
|
|
39
|
-
const rawHeight = Number(params.height ?? params.viewportHeight);
|
|
40
|
-
const hasTargetViewport = Number.isFinite(rawWidth) && Number.isFinite(rawHeight);
|
|
41
|
-
const width = hasTargetViewport ? Math.max(320, rawWidth) : null;
|
|
42
|
-
const height = hasTargetViewport ? Math.max(240, rawHeight) : null;
|
|
43
|
-
const followWindow = params.followWindow !== false;
|
|
44
|
-
const fitDisplayWindow = params.fitDisplayWindow !== false;
|
|
45
|
-
const settleMs = Math.max(0, Number(params.settleMs ?? 180) || 180);
|
|
46
|
-
const attempts = Math.max(1, Number(params.attempts ?? 3) || 3);
|
|
47
|
-
const tolerance = Math.max(0, Number(params.tolerancePx ?? 3) || 3);
|
|
48
|
-
const apiTimeoutMs = Math.max(1000, Number(params.apiTimeoutMs ?? 8000) || 8000);
|
|
49
|
-
|
|
50
|
-
const probeWindow = async () => {
|
|
51
|
-
const probe = await callWithTimeout('evaluate', {
|
|
52
|
-
profileId,
|
|
53
|
-
script: '({ innerWidth: window.innerWidth, innerHeight: window.innerHeight, outerWidth: window.outerWidth, outerHeight: window.outerHeight })',
|
|
54
|
-
}, apiTimeoutMs);
|
|
55
|
-
return probe?.result || {};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
let measured = await probeWindow();
|
|
59
|
-
if (followWindow && !hasTargetViewport) {
|
|
60
|
-
let resizedWindow = false;
|
|
61
|
-
let windowTarget = null;
|
|
62
|
-
if (fitDisplayWindow) {
|
|
63
|
-
try {
|
|
64
|
-
const displayPayload = await callWithTimeout('system:display', {}, apiTimeoutMs);
|
|
65
|
-
const display = displayPayload?.metrics || displayPayload || {};
|
|
66
|
-
const reserveFromEnv = Number(process.env.CAMO_DEFAULT_WINDOW_VERTICAL_RESERVE ?? 0);
|
|
67
|
-
const reservePx = Number.isFinite(reserveFromEnv) ? Math.max(0, Math.min(240, Math.floor(reserveFromEnv))) : 0;
|
|
68
|
-
const workWidth = Number(display.workWidth || 0);
|
|
69
|
-
const workHeight = Number(display.workHeight || 0);
|
|
70
|
-
const displayWidth = Number(display.width || 0);
|
|
71
|
-
const displayHeight = Number(display.height || 0);
|
|
72
|
-
const baseW = Math.floor(workWidth > 0 ? workWidth : displayWidth);
|
|
73
|
-
const baseH = Math.floor(workHeight > 0 ? workHeight : displayHeight);
|
|
74
|
-
if (baseW > 0 && baseH > 0) {
|
|
75
|
-
windowTarget = {
|
|
76
|
-
width: Math.max(960, baseW),
|
|
77
|
-
height: Math.max(700, baseH - reservePx),
|
|
78
|
-
};
|
|
79
|
-
const currentOuterWidth = Number(measured.outerWidth || 0);
|
|
80
|
-
const currentOuterHeight = Number(measured.outerHeight || 0);
|
|
81
|
-
const shouldResize = (
|
|
82
|
-
!Number.isFinite(currentOuterWidth)
|
|
83
|
-
|| !Number.isFinite(currentOuterHeight)
|
|
84
|
-
|| currentOuterWidth < Math.floor(windowTarget.width * 0.92)
|
|
85
|
-
|| currentOuterHeight < Math.floor(windowTarget.height * 0.92)
|
|
86
|
-
);
|
|
87
|
-
if (shouldResize) {
|
|
88
|
-
await callWithTimeout('window:resize', {
|
|
89
|
-
profileId,
|
|
90
|
-
width: windowTarget.width,
|
|
91
|
-
height: windowTarget.height,
|
|
92
|
-
}, apiTimeoutMs);
|
|
93
|
-
if (settleMs > 0) await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
94
|
-
measured = await probeWindow();
|
|
95
|
-
resizedWindow = true;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// display probing is best-effort and must not block follow-window sync
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const innerWidth = Math.max(320, Number(measured.innerWidth || 0) || 1280);
|
|
104
|
-
const innerHeight = Math.max(240, Number(measured.innerHeight || 0) || 720);
|
|
105
|
-
const outerWidth = Math.max(320, Number(measured.outerWidth || 0) || innerWidth);
|
|
106
|
-
const outerHeight = Math.max(240, Number(measured.outerHeight || 0) || innerHeight);
|
|
107
|
-
const rawDeltaW = Math.max(0, outerWidth - innerWidth);
|
|
108
|
-
const rawDeltaH = Math.max(0, outerHeight - innerHeight);
|
|
109
|
-
const frameW = rawDeltaW > 400 ? 16 : Math.min(rawDeltaW, 120);
|
|
110
|
-
const frameH = rawDeltaH > 400 ? 88 : Math.min(rawDeltaH, 180);
|
|
111
|
-
const followWidth = Math.max(320, outerWidth - frameW);
|
|
112
|
-
const followHeight = Math.max(240, outerHeight - frameH);
|
|
113
|
-
|
|
114
|
-
await callWithTimeout('page:setViewport', { profileId, width: followWidth, height: followHeight }, apiTimeoutMs);
|
|
115
|
-
const synced = await probeWindow();
|
|
116
|
-
return {
|
|
117
|
-
ok: true,
|
|
118
|
-
code: 'OPERATION_DONE',
|
|
119
|
-
message: 'sync_window_viewport follow window done',
|
|
120
|
-
data: {
|
|
121
|
-
followWindow: true,
|
|
122
|
-
viewport: { width: followWidth, height: followHeight },
|
|
123
|
-
frame: { width: frameW, height: frameH },
|
|
124
|
-
resizedWindow,
|
|
125
|
-
windowTarget,
|
|
126
|
-
measured: synced,
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!hasTargetViewport) {
|
|
132
|
-
return asErrorPayload('OPERATION_FAILED', 'sync_window_viewport requires width/height when followWindow=false');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
await callWithTimeout('page:setViewport', { profileId, width, height }, apiTimeoutMs);
|
|
136
|
-
|
|
137
|
-
for (let i = 0; i < attempts; i += 1) {
|
|
138
|
-
measured = await probeWindow();
|
|
139
|
-
const innerWidth = Number(measured.innerWidth || 0);
|
|
140
|
-
const innerHeight = Number(measured.innerHeight || 0);
|
|
141
|
-
const widthOk = Math.abs(innerWidth - width) <= tolerance;
|
|
142
|
-
const heightOk = Math.abs(innerHeight - height) <= tolerance;
|
|
143
|
-
if (widthOk && heightOk) {
|
|
144
|
-
return {
|
|
145
|
-
ok: true,
|
|
146
|
-
code: 'OPERATION_DONE',
|
|
147
|
-
message: 'sync_window_viewport done',
|
|
148
|
-
data: {
|
|
149
|
-
width,
|
|
150
|
-
height,
|
|
151
|
-
followWindow,
|
|
152
|
-
attempts: i + 1,
|
|
153
|
-
measured,
|
|
154
|
-
matched: true,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const outerWidth = Number(measured.outerWidth || 0);
|
|
160
|
-
const outerHeight = Number(measured.outerHeight || 0);
|
|
161
|
-
const targetWindowWidth = Math.max(width + 16, outerWidth + (width - innerWidth));
|
|
162
|
-
const targetWindowHeight = Math.max(height + 80, outerHeight + (height - innerHeight));
|
|
163
|
-
await callWithTimeout('window:resize', {
|
|
164
|
-
profileId,
|
|
165
|
-
width: Math.round(targetWindowWidth),
|
|
166
|
-
height: Math.round(targetWindowHeight),
|
|
167
|
-
}, apiTimeoutMs);
|
|
168
|
-
if (settleMs > 0) await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
169
|
-
await callWithTimeout('page:setViewport', { profileId, width, height }, apiTimeoutMs);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
ok: true,
|
|
174
|
-
code: 'OPERATION_DONE',
|
|
175
|
-
message: 'sync_window_viewport best effort done',
|
|
176
|
-
data: {
|
|
177
|
-
width,
|
|
178
|
-
height,
|
|
179
|
-
followWindow,
|
|
180
|
-
attempts,
|
|
181
|
-
measured,
|
|
182
|
-
matched: Math.abs(Number(measured.innerWidth || 0) - width) <= tolerance
|
|
183
|
-
&& Math.abs(Number(measured.innerHeight || 0) - height) <= tolerance,
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return asErrorPayload('UNSUPPORTED_OPERATION', `Unsupported viewport operation: ${action}`);
|
|
189
|
-
}
|
|
1
|
+
import { callAPI } from '../../../utils/browser-service.mjs';
|
|
2
|
+
import { asErrorPayload, getCurrentUrl, normalizeArray } from '../utils.mjs';
|
|
3
|
+
|
|
4
|
+
function callWithTimeout(action, payload, timeoutMs) {
|
|
5
|
+
let timer = null;
|
|
6
|
+
return Promise.race([
|
|
7
|
+
callAPI(action, payload),
|
|
8
|
+
new Promise((_, reject) => {
|
|
9
|
+
timer = setTimeout(() => {
|
|
10
|
+
reject(new Error(`${action} timeout after ${timeoutMs}ms`));
|
|
11
|
+
}, timeoutMs);
|
|
12
|
+
}),
|
|
13
|
+
]).finally(() => {
|
|
14
|
+
if (timer) clearTimeout(timer);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function executeViewportOperation({ profileId, action, params = {} }) {
|
|
19
|
+
if (action === 'get_current_url') {
|
|
20
|
+
const url = await getCurrentUrl(profileId);
|
|
21
|
+
const includes = normalizeArray(params.includes || params.urlIncludes).map((item) => String(item));
|
|
22
|
+
const excludes = normalizeArray(params.excludes || params.urlExcludes).map((item) => String(item));
|
|
23
|
+
const missing = includes.filter((token) => !url.includes(token));
|
|
24
|
+
const forbidden = excludes.filter((token) => url.includes(token));
|
|
25
|
+
if (missing.length > 0 || forbidden.length > 0) {
|
|
26
|
+
return asErrorPayload('URL_MISMATCH', 'current url validation failed', {
|
|
27
|
+
url,
|
|
28
|
+
includes,
|
|
29
|
+
excludes,
|
|
30
|
+
missing,
|
|
31
|
+
forbidden,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { ok: true, code: 'OPERATION_DONE', message: 'get_current_url done', data: { url } };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (action === 'sync_window_viewport') {
|
|
38
|
+
const rawWidth = Number(params.width ?? params.viewportWidth);
|
|
39
|
+
const rawHeight = Number(params.height ?? params.viewportHeight);
|
|
40
|
+
const hasTargetViewport = Number.isFinite(rawWidth) && Number.isFinite(rawHeight);
|
|
41
|
+
const width = hasTargetViewport ? Math.max(320, rawWidth) : null;
|
|
42
|
+
const height = hasTargetViewport ? Math.max(240, rawHeight) : null;
|
|
43
|
+
const followWindow = params.followWindow !== false;
|
|
44
|
+
const fitDisplayWindow = params.fitDisplayWindow !== false;
|
|
45
|
+
const settleMs = Math.max(0, Number(params.settleMs ?? 180) || 180);
|
|
46
|
+
const attempts = Math.max(1, Number(params.attempts ?? 3) || 3);
|
|
47
|
+
const tolerance = Math.max(0, Number(params.tolerancePx ?? 3) || 3);
|
|
48
|
+
const apiTimeoutMs = Math.max(1000, Number(params.apiTimeoutMs ?? 8000) || 8000);
|
|
49
|
+
|
|
50
|
+
const probeWindow = async () => {
|
|
51
|
+
const probe = await callWithTimeout('evaluate', {
|
|
52
|
+
profileId,
|
|
53
|
+
script: '({ innerWidth: window.innerWidth, innerHeight: window.innerHeight, outerWidth: window.outerWidth, outerHeight: window.outerHeight })',
|
|
54
|
+
}, apiTimeoutMs);
|
|
55
|
+
return probe?.result || {};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let measured = await probeWindow();
|
|
59
|
+
if (followWindow && !hasTargetViewport) {
|
|
60
|
+
let resizedWindow = false;
|
|
61
|
+
let windowTarget = null;
|
|
62
|
+
if (fitDisplayWindow) {
|
|
63
|
+
try {
|
|
64
|
+
const displayPayload = await callWithTimeout('system:display', {}, apiTimeoutMs);
|
|
65
|
+
const display = displayPayload?.metrics || displayPayload || {};
|
|
66
|
+
const reserveFromEnv = Number(process.env.CAMO_DEFAULT_WINDOW_VERTICAL_RESERVE ?? 0);
|
|
67
|
+
const reservePx = Number.isFinite(reserveFromEnv) ? Math.max(0, Math.min(240, Math.floor(reserveFromEnv))) : 0;
|
|
68
|
+
const workWidth = Number(display.workWidth || 0);
|
|
69
|
+
const workHeight = Number(display.workHeight || 0);
|
|
70
|
+
const displayWidth = Number(display.width || 0);
|
|
71
|
+
const displayHeight = Number(display.height || 0);
|
|
72
|
+
const baseW = Math.floor(workWidth > 0 ? workWidth : displayWidth);
|
|
73
|
+
const baseH = Math.floor(workHeight > 0 ? workHeight : displayHeight);
|
|
74
|
+
if (baseW > 0 && baseH > 0) {
|
|
75
|
+
windowTarget = {
|
|
76
|
+
width: Math.max(960, baseW),
|
|
77
|
+
height: Math.max(700, baseH - reservePx),
|
|
78
|
+
};
|
|
79
|
+
const currentOuterWidth = Number(measured.outerWidth || 0);
|
|
80
|
+
const currentOuterHeight = Number(measured.outerHeight || 0);
|
|
81
|
+
const shouldResize = (
|
|
82
|
+
!Number.isFinite(currentOuterWidth)
|
|
83
|
+
|| !Number.isFinite(currentOuterHeight)
|
|
84
|
+
|| currentOuterWidth < Math.floor(windowTarget.width * 0.92)
|
|
85
|
+
|| currentOuterHeight < Math.floor(windowTarget.height * 0.92)
|
|
86
|
+
);
|
|
87
|
+
if (shouldResize) {
|
|
88
|
+
await callWithTimeout('window:resize', {
|
|
89
|
+
profileId,
|
|
90
|
+
width: windowTarget.width,
|
|
91
|
+
height: windowTarget.height,
|
|
92
|
+
}, apiTimeoutMs);
|
|
93
|
+
if (settleMs > 0) await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
94
|
+
measured = await probeWindow();
|
|
95
|
+
resizedWindow = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// display probing is best-effort and must not block follow-window sync
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const innerWidth = Math.max(320, Number(measured.innerWidth || 0) || 1280);
|
|
104
|
+
const innerHeight = Math.max(240, Number(measured.innerHeight || 0) || 720);
|
|
105
|
+
const outerWidth = Math.max(320, Number(measured.outerWidth || 0) || innerWidth);
|
|
106
|
+
const outerHeight = Math.max(240, Number(measured.outerHeight || 0) || innerHeight);
|
|
107
|
+
const rawDeltaW = Math.max(0, outerWidth - innerWidth);
|
|
108
|
+
const rawDeltaH = Math.max(0, outerHeight - innerHeight);
|
|
109
|
+
const frameW = rawDeltaW > 400 ? 16 : Math.min(rawDeltaW, 120);
|
|
110
|
+
const frameH = rawDeltaH > 400 ? 88 : Math.min(rawDeltaH, 180);
|
|
111
|
+
const followWidth = Math.max(320, outerWidth - frameW);
|
|
112
|
+
const followHeight = Math.max(240, outerHeight - frameH);
|
|
113
|
+
|
|
114
|
+
await callWithTimeout('page:setViewport', { profileId, width: followWidth, height: followHeight }, apiTimeoutMs);
|
|
115
|
+
const synced = await probeWindow();
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
code: 'OPERATION_DONE',
|
|
119
|
+
message: 'sync_window_viewport follow window done',
|
|
120
|
+
data: {
|
|
121
|
+
followWindow: true,
|
|
122
|
+
viewport: { width: followWidth, height: followHeight },
|
|
123
|
+
frame: { width: frameW, height: frameH },
|
|
124
|
+
resizedWindow,
|
|
125
|
+
windowTarget,
|
|
126
|
+
measured: synced,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!hasTargetViewport) {
|
|
132
|
+
return asErrorPayload('OPERATION_FAILED', 'sync_window_viewport requires width/height when followWindow=false');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await callWithTimeout('page:setViewport', { profileId, width, height }, apiTimeoutMs);
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
138
|
+
measured = await probeWindow();
|
|
139
|
+
const innerWidth = Number(measured.innerWidth || 0);
|
|
140
|
+
const innerHeight = Number(measured.innerHeight || 0);
|
|
141
|
+
const widthOk = Math.abs(innerWidth - width) <= tolerance;
|
|
142
|
+
const heightOk = Math.abs(innerHeight - height) <= tolerance;
|
|
143
|
+
if (widthOk && heightOk) {
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
code: 'OPERATION_DONE',
|
|
147
|
+
message: 'sync_window_viewport done',
|
|
148
|
+
data: {
|
|
149
|
+
width,
|
|
150
|
+
height,
|
|
151
|
+
followWindow,
|
|
152
|
+
attempts: i + 1,
|
|
153
|
+
measured,
|
|
154
|
+
matched: true,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const outerWidth = Number(measured.outerWidth || 0);
|
|
160
|
+
const outerHeight = Number(measured.outerHeight || 0);
|
|
161
|
+
const targetWindowWidth = Math.max(width + 16, outerWidth + (width - innerWidth));
|
|
162
|
+
const targetWindowHeight = Math.max(height + 80, outerHeight + (height - innerHeight));
|
|
163
|
+
await callWithTimeout('window:resize', {
|
|
164
|
+
profileId,
|
|
165
|
+
width: Math.round(targetWindowWidth),
|
|
166
|
+
height: Math.round(targetWindowHeight),
|
|
167
|
+
}, apiTimeoutMs);
|
|
168
|
+
if (settleMs > 0) await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
169
|
+
await callWithTimeout('page:setViewport', { profileId, width, height }, apiTimeoutMs);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
ok: true,
|
|
174
|
+
code: 'OPERATION_DONE',
|
|
175
|
+
message: 'sync_window_viewport best effort done',
|
|
176
|
+
data: {
|
|
177
|
+
width,
|
|
178
|
+
height,
|
|
179
|
+
followWindow,
|
|
180
|
+
attempts,
|
|
181
|
+
measured,
|
|
182
|
+
matched: Math.abs(Number(measured.innerWidth || 0) - width) <= tolerance
|
|
183
|
+
&& Math.abs(Number(measured.innerHeight || 0) - height) <= tolerance,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return asErrorPayload('UNSUPPORTED_OPERATION', `Unsupported viewport operation: ${action}`);
|
|
189
|
+
}
|