@effect-agent/platform-cloudflare 0.1.0-beta.35 → 0.1.0-beta.37
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/dist/index.d.mts +4 -98
- package/dist/index.mjs +8 -817
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +27 -5
- package/dist/interactive-browser.mjs +530 -69
- package/dist/interactive-browser.mjs.map +1 -1
- package/dist/scheduling-B-OFqoS9.mjs +1189 -0
- package/dist/scheduling-B-OFqoS9.mjs.map +1 -0
- package/dist/scheduling-BJs_kHTx.d.mts +142 -0
- package/dist/scheduling.d.mts +2 -0
- package/dist/scheduling.mjs +2 -0
- package/package.json +11 -10
- package/src/alarm.ts +19 -2
- package/src/index.ts +1 -0
- package/src/interactive-browser.ts +843 -75
- package/src/layers.ts +1 -1
- package/src/scheduling.ts +676 -0
|
@@ -16,6 +16,10 @@ const MAX_HANDOFF_TIMEOUT_MILLIS = 30 * 6e4;
|
|
|
16
16
|
const MAX_HOST_TEXT_LENGTH = 8 * 1024;
|
|
17
17
|
const CLEANUP_STEP_TIMEOUT_MILLIS = 1e4;
|
|
18
18
|
const CLOSE_SESSION_TIMEOUT_MILLIS = 1e4;
|
|
19
|
+
const ACTION_NETWORK_QUIET_MILLIS = 200;
|
|
20
|
+
const ACTION_NETWORK_SETTLE_MILLIS = 2e3;
|
|
21
|
+
const ACTION_POST_STATE_MILLIS = 250;
|
|
22
|
+
const MAX_OBSERVED_CONTROLS = 64;
|
|
19
23
|
const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0));
|
|
20
24
|
const BoundedHostText = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(MAX_HOST_TEXT_LENGTH));
|
|
21
25
|
const BoundedRemoteText = Schema.String.check(Schema.isMaxLength(MAX_TEXT_LENGTH));
|
|
@@ -30,6 +34,57 @@ const TextObservation = Schema.Union([
|
|
|
30
34
|
observed: Schema.Natural
|
|
31
35
|
})
|
|
32
36
|
]);
|
|
37
|
+
const ActionTargetState = Schema.Struct({
|
|
38
|
+
matchCount: Schema.Natural,
|
|
39
|
+
invalidSelector: Schema.optionalKey(Schema.Boolean),
|
|
40
|
+
kind: Schema.optionalKey(Schema.Literals([
|
|
41
|
+
"button",
|
|
42
|
+
"checkbox",
|
|
43
|
+
"radio",
|
|
44
|
+
"select",
|
|
45
|
+
"text",
|
|
46
|
+
"link",
|
|
47
|
+
"other"
|
|
48
|
+
])),
|
|
49
|
+
checked: Schema.optionalKey(Schema.Boolean),
|
|
50
|
+
selected: Schema.optionalKey(Schema.Boolean),
|
|
51
|
+
disabled: Schema.optionalKey(Schema.Boolean),
|
|
52
|
+
required: Schema.optionalKey(Schema.Boolean),
|
|
53
|
+
valid: Schema.optionalKey(Schema.Boolean),
|
|
54
|
+
formValid: Schema.optionalKey(Schema.Boolean)
|
|
55
|
+
});
|
|
56
|
+
const ActionNetworkState = Schema.Struct({
|
|
57
|
+
total: Schema.Natural,
|
|
58
|
+
status2xx: Schema.Natural,
|
|
59
|
+
status3xx: Schema.Natural,
|
|
60
|
+
status4xx: Schema.Natural,
|
|
61
|
+
status5xx: Schema.Natural,
|
|
62
|
+
failed: Schema.Natural,
|
|
63
|
+
pending: Schema.Natural,
|
|
64
|
+
settleTimedOut: Schema.Boolean
|
|
65
|
+
});
|
|
66
|
+
const ActionObservation = Schema.Struct({
|
|
67
|
+
before: ActionTargetState,
|
|
68
|
+
after: Schema.optionalKey(ActionTargetState),
|
|
69
|
+
afterUnavailable: Schema.Boolean,
|
|
70
|
+
network: ActionNetworkState
|
|
71
|
+
});
|
|
72
|
+
const PageObservation = Schema.fromJsonString(Schema.Struct({
|
|
73
|
+
pageText: BoundedRemoteText,
|
|
74
|
+
selectorMatchCount: Schema.Natural,
|
|
75
|
+
controlsTruncated: Schema.Boolean,
|
|
76
|
+
controls: Schema.Array(Schema.Struct({
|
|
77
|
+
selector: BoundedRemoteText,
|
|
78
|
+
kind: BoundedRemoteText,
|
|
79
|
+
label: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(200))),
|
|
80
|
+
checked: Schema.optionalKey(Schema.Boolean),
|
|
81
|
+
selected: Schema.optionalKey(Schema.Boolean),
|
|
82
|
+
disabled: Schema.optionalKey(Schema.Boolean),
|
|
83
|
+
required: Schema.optionalKey(Schema.Boolean),
|
|
84
|
+
valid: Schema.optionalKey(Schema.Boolean),
|
|
85
|
+
formValid: Schema.optionalKey(Schema.Boolean)
|
|
86
|
+
})).check(Schema.isMaxLength(MAX_OBSERVED_CONTROLS))
|
|
87
|
+
}));
|
|
33
88
|
const PngBytes = Schema.Uint8Array.check(Schema.isMaxLength(MAX_SCREENSHOT_BYTES), Schema.makeFilter((bytes) => bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10, { title: "PNG bytes" }));
|
|
34
89
|
const BrowserRunSessionId = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256), Schema.makeFilter((value) => /^[A-Za-z0-9_-]+$/.test(value), { title: "a Browser Run session identifier" }));
|
|
35
90
|
const LiveViewUrl = Schema.String.check(Schema.isMaxLength(MAX_HOST_TEXT_LENGTH), Schema.makeFilter((value) => {
|
|
@@ -52,6 +107,24 @@ const HandoffStateObservation = Schema.Union([Schema.Struct({
|
|
|
52
107
|
handoffId: Schema.optionalKey(BoundedHostText),
|
|
53
108
|
durationMs: Schema.optionalKey(HandoffDuration)
|
|
54
109
|
})]);
|
|
110
|
+
/**
|
|
111
|
+
* Host presentation state in CSS pixels. Dimensions are integers in 1..2048,
|
|
112
|
+
* density is finite in 1..2 (default 1), and neither scaled dimension may exceed
|
|
113
|
+
* 2048 pixels. Mobile, touch, and orientation emulation are not supported.
|
|
114
|
+
*/
|
|
115
|
+
var BrowserRunViewport = class extends Schema.Class("BrowserRunViewport")(Schema.Struct({
|
|
116
|
+
width: PositiveInt.check(Schema.isLessThanOrEqualTo(2048)),
|
|
117
|
+
height: PositiveInt.check(Schema.isLessThanOrEqualTo(2048)),
|
|
118
|
+
deviceScaleFactor: Schema.optionalKey(Schema.Finite.check(Schema.isBetween({
|
|
119
|
+
minimum: 1,
|
|
120
|
+
maximum: 2
|
|
121
|
+
})))
|
|
122
|
+
}).check(Schema.makeFilter((viewport) => Math.max(viewport.width, viewport.height) * (viewport.deviceScaleFactor ?? 1) <= 2048, { title: "a viewport with scaled dimensions no larger than 2048 pixels" }))) {};
|
|
123
|
+
const decodeViewport = (input) => Schema.decodeUnknownEffect(BrowserRunViewport)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => policyError("The browser viewport is malformed")), Effect.map((viewport) => ({
|
|
124
|
+
width: viewport.width,
|
|
125
|
+
height: viewport.height,
|
|
126
|
+
deviceScaleFactor: viewport.deviceScaleFactor ?? 1
|
|
127
|
+
})));
|
|
55
128
|
/** Host-only request for a redacted Cloudflare Live View URL. */
|
|
56
129
|
var BrowserRunLiveViewRequest = class extends Schema.Class("BrowserRunLiveViewRequest")({
|
|
57
130
|
mode: Schema.Literal("tab"),
|
|
@@ -75,10 +148,16 @@ var BrowserRunHandoffState = class extends Schema.Class("BrowserRunHandoffState"
|
|
|
75
148
|
/** Host-supplied Browser Run binding projected into one fakeable launch operation. */
|
|
76
149
|
var BrowserRunInteractiveBinding = class BrowserRunInteractiveBinding extends Context.Service()("@effect-agent/platform-cloudflare/BrowserRunInteractiveBinding") {
|
|
77
150
|
static layer(options) {
|
|
78
|
-
return Layer.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
151
|
+
return Layer.effect(BrowserRunInteractiveBinding)(Effect.gen(function* () {
|
|
152
|
+
const viewport = options.viewport === void 0 ? void 0 : yield* decodeViewport(options.viewport);
|
|
153
|
+
return {
|
|
154
|
+
launch: async (keepAliveMillis) => makeProductionBrowser(await puppeteer.launch(options.browser, {
|
|
155
|
+
keep_alive: keepAliveMillis,
|
|
156
|
+
...viewport === void 0 ? {} : { defaultViewport: { ...viewport } }
|
|
157
|
+
})),
|
|
158
|
+
connect: async (sessionId) => makeProductionBrowser(await puppeteer.connect(options.browser, sessionId))
|
|
159
|
+
};
|
|
160
|
+
}));
|
|
82
161
|
}
|
|
83
162
|
};
|
|
84
163
|
/** Cloudflare host authority kept separate from the provider-neutral browser handle. */
|
|
@@ -95,6 +174,205 @@ const makeProductionCdpSession = (session) => ({
|
|
|
95
174
|
},
|
|
96
175
|
detach: () => session.detach()
|
|
97
176
|
});
|
|
177
|
+
var BrowserRunActionUndispatched = class extends Error {
|
|
178
|
+
matchCount;
|
|
179
|
+
constructor(matchCount) {
|
|
180
|
+
super("The browser action selector did not resolve to exactly one element");
|
|
181
|
+
this.matchCount = matchCount;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const readActionTarget = (page, selector) => page.evaluate((requestedSelector) => {
|
|
185
|
+
const pageDocument = Reflect.get(globalThis, "document");
|
|
186
|
+
let matches;
|
|
187
|
+
try {
|
|
188
|
+
matches = Reflect.apply(Reflect.get(pageDocument, "querySelectorAll"), pageDocument, [requestedSelector]);
|
|
189
|
+
} catch (cause) {
|
|
190
|
+
if (cause instanceof Error && cause.name === "SyntaxError") return {
|
|
191
|
+
matchCount: 0,
|
|
192
|
+
invalidSelector: true
|
|
193
|
+
};
|
|
194
|
+
throw cause;
|
|
195
|
+
}
|
|
196
|
+
if (typeof matches !== "object" || matches === null) throw new Error("Invalid query result");
|
|
197
|
+
const matchCount = Math.min(1e4, Reflect.get(matches, "length"));
|
|
198
|
+
const element = Reflect.get(matches, 0);
|
|
199
|
+
if (element === void 0) return { matchCount };
|
|
200
|
+
const associated = Reflect.get(element, "control") ?? element;
|
|
201
|
+
const tagName = String(Reflect.get(associated, "tagName") ?? "").toLowerCase();
|
|
202
|
+
const inputType = String(Reflect.get(associated, "type") ?? "").toLowerCase();
|
|
203
|
+
const role = String(Reflect.apply(Reflect.get(element, "getAttribute"), element, ["role"]) ?? "").toLowerCase();
|
|
204
|
+
const kind = tagName === "button" || role === "button" ? "button" : inputType === "checkbox" || role === "checkbox" || role === "switch" ? "checkbox" : inputType === "radio" || role === "radio" ? "radio" : tagName === "select" ? "select" : tagName === "input" || tagName === "textarea" ? "text" : tagName === "a" ? "link" : "other";
|
|
205
|
+
const checked = Reflect.get(associated, "checked");
|
|
206
|
+
const selected = tagName === "select" ? Reflect.get(associated, "selectedIndex") >= 0 : Reflect.get(associated, "selected");
|
|
207
|
+
const disabled = Reflect.get(associated, "disabled");
|
|
208
|
+
const required = Reflect.get(associated, "required");
|
|
209
|
+
const validity = Reflect.get(associated, "validity");
|
|
210
|
+
const form = Reflect.get(associated, "form");
|
|
211
|
+
const formMatches = form === null || form === void 0 ? void 0 : Reflect.get(form, "matches");
|
|
212
|
+
const ariaChecked = Reflect.apply(Reflect.get(element, "getAttribute"), element, ["aria-checked"]);
|
|
213
|
+
const ariaDisabled = Reflect.apply(Reflect.get(element, "getAttribute"), element, ["aria-disabled"]);
|
|
214
|
+
const ariaSelected = Reflect.apply(Reflect.get(element, "getAttribute"), element, ["aria-selected"]);
|
|
215
|
+
return {
|
|
216
|
+
matchCount,
|
|
217
|
+
kind,
|
|
218
|
+
...typeof checked === "boolean" ? { checked } : ariaChecked === "true" || ariaChecked === "false" ? { checked: ariaChecked === "true" } : {},
|
|
219
|
+
...typeof selected === "boolean" ? { selected } : ariaSelected === "true" || ariaSelected === "false" ? { selected: ariaSelected === "true" } : {},
|
|
220
|
+
disabled: typeof disabled === "boolean" ? disabled : ariaDisabled === "true",
|
|
221
|
+
...typeof required === "boolean" ? { required } : {},
|
|
222
|
+
...validity !== void 0 && typeof Reflect.get(validity, "valid") === "boolean" ? { valid: Reflect.get(validity, "valid") } : {},
|
|
223
|
+
...typeof formMatches === "function" ? { formValid: Reflect.apply(formMatches, form, [":valid"]) } : {}
|
|
224
|
+
};
|
|
225
|
+
}, selector);
|
|
226
|
+
const boundedBestEffort = async (promise, millis) => {
|
|
227
|
+
let timer;
|
|
228
|
+
try {
|
|
229
|
+
return await Promise.race([promise.catch(() => void 0), new Promise((resolve) => {
|
|
230
|
+
timer = setTimeout(resolve, millis);
|
|
231
|
+
})]);
|
|
232
|
+
} finally {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const readActionTargetAfter = (page, selector) => boundedBestEffort(readActionTarget(page, selector).then(Schema.decodeUnknownSync(ActionTargetState)), ACTION_POST_STATE_MILLIS);
|
|
237
|
+
const makeActionRequestTracker = (page, signal) => {
|
|
238
|
+
const pending = /* @__PURE__ */ new Set();
|
|
239
|
+
let total = 0;
|
|
240
|
+
let status2xx = 0;
|
|
241
|
+
let status3xx = 0;
|
|
242
|
+
let status4xx = 0;
|
|
243
|
+
let status5xx = 0;
|
|
244
|
+
let failed = 0;
|
|
245
|
+
let lastChange = performance.now();
|
|
246
|
+
let closed = false;
|
|
247
|
+
let wake;
|
|
248
|
+
let timer;
|
|
249
|
+
const relevant = (request) => {
|
|
250
|
+
const resourceType = request.resourceType();
|
|
251
|
+
return resourceType === "fetch" || resourceType === "xhr";
|
|
252
|
+
};
|
|
253
|
+
const onRequest = (request) => {
|
|
254
|
+
if (!relevant(request)) return;
|
|
255
|
+
pending.add(request);
|
|
256
|
+
total++;
|
|
257
|
+
lastChange = performance.now();
|
|
258
|
+
};
|
|
259
|
+
const onFinished = (request) => {
|
|
260
|
+
if (!pending.delete(request)) return;
|
|
261
|
+
let status;
|
|
262
|
+
try {
|
|
263
|
+
status = request.response()?.status();
|
|
264
|
+
} catch {}
|
|
265
|
+
if (status !== void 0) {
|
|
266
|
+
if (status >= 200 && status < 300) status2xx++;
|
|
267
|
+
else if (status >= 300 && status < 400) status3xx++;
|
|
268
|
+
else if (status >= 400 && status < 500) status4xx++;
|
|
269
|
+
else if (status >= 500 && status < 600) status5xx++;
|
|
270
|
+
}
|
|
271
|
+
lastChange = performance.now();
|
|
272
|
+
};
|
|
273
|
+
const onFailed = (request) => {
|
|
274
|
+
if (!pending.delete(request)) return;
|
|
275
|
+
failed++;
|
|
276
|
+
lastChange = performance.now();
|
|
277
|
+
};
|
|
278
|
+
const close = () => {
|
|
279
|
+
if (closed) return;
|
|
280
|
+
closed = true;
|
|
281
|
+
page.off("request", onRequest);
|
|
282
|
+
page.off("requestfinished", onFinished);
|
|
283
|
+
page.off("requestfailed", onFailed);
|
|
284
|
+
signal.removeEventListener("abort", close);
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
wake?.();
|
|
287
|
+
};
|
|
288
|
+
try {
|
|
289
|
+
page.on("request", onRequest);
|
|
290
|
+
page.on("requestfinished", onFinished);
|
|
291
|
+
page.on("requestfailed", onFailed);
|
|
292
|
+
signal.addEventListener("abort", close, { once: true });
|
|
293
|
+
if (signal.aborted) close();
|
|
294
|
+
} catch (cause) {
|
|
295
|
+
close();
|
|
296
|
+
throw cause;
|
|
297
|
+
}
|
|
298
|
+
const wait = async () => {
|
|
299
|
+
const startedAt = performance.now();
|
|
300
|
+
let settleTimedOut = false;
|
|
301
|
+
while (!closed) {
|
|
302
|
+
const now = performance.now();
|
|
303
|
+
if (pending.size === 0 && now - lastChange >= ACTION_NETWORK_QUIET_MILLIS) break;
|
|
304
|
+
if (now - startedAt >= ACTION_NETWORK_SETTLE_MILLIS) {
|
|
305
|
+
settleTimedOut = true;
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
await new Promise((resolve) => {
|
|
309
|
+
wake = resolve;
|
|
310
|
+
timer = setTimeout(() => resolve(), 50);
|
|
311
|
+
});
|
|
312
|
+
wake = void 0;
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
total,
|
|
316
|
+
status2xx,
|
|
317
|
+
status3xx,
|
|
318
|
+
status4xx,
|
|
319
|
+
status5xx,
|
|
320
|
+
failed,
|
|
321
|
+
pending: pending.size,
|
|
322
|
+
settleTimedOut
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
wait,
|
|
327
|
+
close,
|
|
328
|
+
markActionSettled: () => {
|
|
329
|
+
lastChange = performance.now();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
const disposeActionHandles = async (handles) => {
|
|
334
|
+
await boundedBestEffort(Promise.allSettled(handles.map(async (handle) => handle.dispose())), ACTION_POST_STATE_MILLIS);
|
|
335
|
+
};
|
|
336
|
+
const runObservedPageAction = async (page, selector, signal, onDispatch, action) => {
|
|
337
|
+
if (signal.aborted) throw new BrowserRunActionUndispatched(0);
|
|
338
|
+
const before = Schema.decodeUnknownSync(ActionTargetState)(await readActionTarget(page, selector));
|
|
339
|
+
if (before.matchCount !== 1 || before.invalidSelector === true) throw new BrowserRunActionUndispatched(before.matchCount);
|
|
340
|
+
const matches = await page.$$(selector);
|
|
341
|
+
if (matches.length !== 1 || matches[0] === void 0) {
|
|
342
|
+
await disposeActionHandles(matches);
|
|
343
|
+
throw new BrowserRunActionUndispatched(Math.min(1e4, matches.length));
|
|
344
|
+
}
|
|
345
|
+
if (signal.aborted) {
|
|
346
|
+
await disposeActionHandles(matches);
|
|
347
|
+
throw new BrowserRunActionUndispatched(1);
|
|
348
|
+
}
|
|
349
|
+
let tracker;
|
|
350
|
+
let disposing;
|
|
351
|
+
const dispose = () => disposing ??= disposeActionHandles(matches);
|
|
352
|
+
const onAbort = () => {
|
|
353
|
+
dispose();
|
|
354
|
+
};
|
|
355
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
356
|
+
try {
|
|
357
|
+
tracker = makeActionRequestTracker(page, signal);
|
|
358
|
+
if (signal.aborted) throw new BrowserRunActionUndispatched(1);
|
|
359
|
+
onDispatch();
|
|
360
|
+
await action(matches[0]);
|
|
361
|
+
tracker.markActionSettled();
|
|
362
|
+
const network = await tracker.wait();
|
|
363
|
+
const after = signal.aborted ? void 0 : await readActionTargetAfter(page, selector);
|
|
364
|
+
return {
|
|
365
|
+
before,
|
|
366
|
+
...after === void 0 ? {} : { after },
|
|
367
|
+
afterUnavailable: after === void 0,
|
|
368
|
+
network
|
|
369
|
+
};
|
|
370
|
+
} finally {
|
|
371
|
+
tracker?.close();
|
|
372
|
+
signal.removeEventListener("abort", onAbort);
|
|
373
|
+
await dispose();
|
|
374
|
+
}
|
|
375
|
+
};
|
|
98
376
|
const makeProductionPage = (page) => {
|
|
99
377
|
const listeners = /* @__PURE__ */ new Map();
|
|
100
378
|
return {
|
|
@@ -115,51 +393,146 @@ const makeProductionPage = (page) => {
|
|
|
115
393
|
},
|
|
116
394
|
goto: async (url) => {
|
|
117
395
|
await page.goto(url, {
|
|
118
|
-
waitUntil: "
|
|
119
|
-
timeout:
|
|
396
|
+
waitUntil: "domcontentloaded",
|
|
397
|
+
timeout: 3e4
|
|
120
398
|
});
|
|
121
399
|
},
|
|
122
400
|
url: () => page.url(),
|
|
123
|
-
readText: (selector, maximumBytes) =>
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
401
|
+
readText: async (selector, maximumBytes) => {
|
|
402
|
+
const observation = Schema.decodeUnknownSync(TextObservation)(await page.evaluate((requestedSelector, maximum, maximumControls) => {
|
|
403
|
+
const pageDocument = Reflect.get(globalThis, "document");
|
|
404
|
+
const matches = requestedSelector === void 0 ? void 0 : Reflect.apply(Reflect.get(pageDocument, "querySelectorAll"), pageDocument, [requestedSelector]);
|
|
405
|
+
const selectorMatchCount = matches === void 0 || matches === null ? 1 : Math.min(1e4, Reflect.get(matches, "length"));
|
|
406
|
+
const element = matches === void 0 || matches === null ? Reflect.get(pageDocument, "body") : Reflect.get(matches, 0);
|
|
407
|
+
if (element === null) return { _tag: "MissingElement" };
|
|
408
|
+
if (element === void 0) return { _tag: "MissingElement" };
|
|
409
|
+
const innerText = Reflect.get(element, "innerText");
|
|
410
|
+
const textContent = Reflect.get(element, "textContent");
|
|
411
|
+
const pageText = typeof innerText === "string" ? innerText : typeof textContent === "string" ? textContent : "";
|
|
412
|
+
const primaryControlSelector = "input,select,textarea,label,button,[role=\"checkbox\"],[role=\"radio\"],[role=\"option\"],[role=\"switch\"],[role=\"tab\"],[role=\"button\"]";
|
|
413
|
+
const optionSelector = "select option";
|
|
414
|
+
const secondaryControlSelector = "a[href]";
|
|
415
|
+
const controlSelector = `${primaryControlSelector},${optionSelector},${secondaryControlSelector}`;
|
|
416
|
+
const selectorFor = (candidate) => {
|
|
417
|
+
const parts = [];
|
|
418
|
+
let current = candidate;
|
|
419
|
+
while (current !== null && current !== void 0) {
|
|
420
|
+
const tagName = String(Reflect.get(current, "tagName") ?? "").toLowerCase();
|
|
421
|
+
if (tagName === "") break;
|
|
422
|
+
const parent = Reflect.get(current, "parentElement");
|
|
423
|
+
if (parent === null) {
|
|
424
|
+
parts.push(tagName);
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
let sibling = Reflect.get(current, "previousElementSibling");
|
|
428
|
+
let index = 1;
|
|
429
|
+
while (sibling !== null && sibling !== void 0) {
|
|
430
|
+
if (String(Reflect.get(sibling, "tagName") ?? "").toLowerCase() === tagName) index++;
|
|
431
|
+
sibling = Reflect.get(sibling, "previousElementSibling");
|
|
432
|
+
}
|
|
433
|
+
parts.push(`${tagName}:nth-of-type(${index})`);
|
|
434
|
+
current = parent;
|
|
148
435
|
}
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
436
|
+
return parts.reverse().join(" > ");
|
|
437
|
+
};
|
|
438
|
+
const visible = (candidate) => {
|
|
439
|
+
const hidden = Reflect.get(candidate, "hidden");
|
|
440
|
+
const ariaHidden = Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["aria-hidden"]);
|
|
441
|
+
const rects = Reflect.apply(Reflect.get(candidate, "getClientRects"), candidate, []);
|
|
442
|
+
return hidden !== true && ariaHidden !== "true" && typeof rects === "object" && rects !== null && Reflect.get(rects, "length") > 0;
|
|
443
|
+
};
|
|
444
|
+
const candidates = [];
|
|
445
|
+
let controlsTruncated = false;
|
|
446
|
+
const consider = (candidate) => {
|
|
447
|
+
const tagName = String(Reflect.get(candidate, "tagName") ?? "").toLowerCase();
|
|
448
|
+
const visibilityTarget = tagName === "option" ? Reflect.apply(Reflect.get(candidate, "closest"), candidate, ["select"]) : candidate;
|
|
449
|
+
if (!(tagName !== "label" || Reflect.get(candidate, "control") !== null) || typeof visibilityTarget !== "object" || visibilityTarget === null || !visible(visibilityTarget)) return;
|
|
450
|
+
candidates.push(candidate);
|
|
451
|
+
if (candidates.length > maximumControls) controlsTruncated = true;
|
|
452
|
+
};
|
|
453
|
+
const elementMatches = Reflect.get(element, "matches");
|
|
454
|
+
if (typeof elementMatches === "function" && Reflect.apply(elementMatches, element, [controlSelector])) consider(element);
|
|
455
|
+
const considerSelector = (candidateSelector) => {
|
|
456
|
+
const descendants = Reflect.apply(Reflect.get(element, "querySelectorAll"), element, [candidateSelector]);
|
|
457
|
+
if (typeof descendants !== "object" || descendants === null) return;
|
|
458
|
+
const descendantCount = Reflect.get(descendants, "length");
|
|
459
|
+
for (let index = 0; index < descendantCount && !controlsTruncated; index++) consider(Reflect.get(descendants, index));
|
|
460
|
+
};
|
|
461
|
+
considerSelector(primaryControlSelector);
|
|
462
|
+
if (!controlsTruncated) considerSelector(optionSelector);
|
|
463
|
+
if (!controlsTruncated) considerSelector(secondaryControlSelector);
|
|
464
|
+
const controls = candidates.slice(0, maximumControls).map((candidate) => {
|
|
465
|
+
const associated = Reflect.get(candidate, "control") ?? candidate;
|
|
466
|
+
const tagName = String(Reflect.get(candidate, "tagName") ?? "").toLowerCase();
|
|
467
|
+
const inputType = String(Reflect.get(associated, "type") ?? "").toLowerCase();
|
|
468
|
+
const role = String(Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["role"]) ?? "").toLowerCase();
|
|
469
|
+
const ariaLabel = Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["aria-label"]);
|
|
470
|
+
const candidateText = tagName === "textarea" || tagName === "input" || tagName === "select" ? void 0 : Reflect.get(candidate, tagName === "option" ? "label" : "innerText");
|
|
471
|
+
const associatedLabels = Reflect.get(associated, "labels");
|
|
472
|
+
const associatedLabel = associatedLabels !== void 0 && associatedLabels !== null && Reflect.get(associatedLabels, "length") > 0 ? Reflect.get(Reflect.get(associatedLabels, 0), "innerText") : void 0;
|
|
473
|
+
const label = String(typeof ariaLabel === "string" && ariaLabel !== "" ? ariaLabel : typeof candidateText === "string" && candidateText !== "" ? candidateText : associatedLabel ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
474
|
+
const checked = Reflect.get(associated, "checked");
|
|
475
|
+
const selected = tagName === "select" ? Reflect.get(associated, "selectedIndex") >= 0 : Reflect.get(associated, "selected");
|
|
476
|
+
const disabled = Reflect.get(associated, "disabled");
|
|
477
|
+
const required = Reflect.get(associated, "required");
|
|
478
|
+
const validity = Reflect.get(associated, "validity");
|
|
479
|
+
const form = Reflect.get(associated, "form");
|
|
480
|
+
const formMatches = form === null || form === void 0 ? void 0 : Reflect.get(form, "matches");
|
|
481
|
+
const ariaChecked = Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["aria-checked"]);
|
|
482
|
+
const ariaSelected = Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["aria-selected"]);
|
|
483
|
+
const ariaDisabled = Reflect.apply(Reflect.get(candidate, "getAttribute"), candidate, ["aria-disabled"]);
|
|
484
|
+
return {
|
|
485
|
+
selector: selectorFor(candidate),
|
|
486
|
+
kind: tagName === "label" ? `label:${inputType || "control"}` : tagName === "input" ? `input:${inputType || "text"}` : role !== "" ? `role:${role}` : tagName,
|
|
487
|
+
...label === "" ? {} : { label },
|
|
488
|
+
...typeof checked === "boolean" ? { checked } : ariaChecked === "true" || ariaChecked === "false" ? { checked: ariaChecked === "true" } : {},
|
|
489
|
+
...typeof selected === "boolean" ? { selected } : ariaSelected === "true" || ariaSelected === "false" ? { selected: ariaSelected === "true" } : {},
|
|
490
|
+
...typeof disabled === "boolean" ? { disabled } : ariaDisabled === "true" || ariaDisabled === "false" ? { disabled: ariaDisabled === "true" } : {},
|
|
491
|
+
...typeof required === "boolean" ? { required } : {},
|
|
492
|
+
...validity !== void 0 && typeof Reflect.get(validity, "valid") === "boolean" ? { valid: Reflect.get(validity, "valid") } : {},
|
|
493
|
+
...typeof formMatches === "function" ? { formValid: Reflect.apply(formMatches, form, [":valid"]) } : {}
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
const text = JSON.stringify({
|
|
497
|
+
pageText,
|
|
498
|
+
selectorMatchCount,
|
|
499
|
+
controls,
|
|
500
|
+
controlsTruncated
|
|
501
|
+
});
|
|
502
|
+
const observed = new TextEncoder().encode(text).byteLength;
|
|
503
|
+
return observed > maximum ? {
|
|
504
|
+
_tag: "OverLimit",
|
|
505
|
+
observed
|
|
506
|
+
} : {
|
|
507
|
+
_tag: "Text",
|
|
508
|
+
text
|
|
509
|
+
};
|
|
510
|
+
}, selector, maximumBytes, MAX_OBSERVED_CONTROLS));
|
|
511
|
+
if (observation._tag === "Text") Schema.decodeUnknownSync(PageObservation)(observation.text);
|
|
512
|
+
return observation;
|
|
161
513
|
},
|
|
162
|
-
|
|
514
|
+
fill: (selector, value, signal, onDispatch) => runObservedPageAction(page, selector, signal, onDispatch, (element) => element.evaluate((element, nextValue) => {
|
|
515
|
+
let prototype = Reflect.getPrototypeOf(element);
|
|
516
|
+
let setValue;
|
|
517
|
+
while (prototype !== null) {
|
|
518
|
+
const setter = Reflect.getOwnPropertyDescriptor(prototype, "value")?.set;
|
|
519
|
+
if (typeof setter === "function") {
|
|
520
|
+
setValue = setter;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
prototype = Reflect.getPrototypeOf(prototype);
|
|
524
|
+
}
|
|
525
|
+
if (setValue === void 0) throw new Error("The selector did not resolve to a fillable field");
|
|
526
|
+
const focus = Reflect.get(element, "focus");
|
|
527
|
+
if (typeof focus === "function") Reflect.apply(focus, element, []);
|
|
528
|
+
Reflect.apply(setValue, element, [nextValue]);
|
|
529
|
+
const dispatchEvent = Reflect.get(element, "dispatchEvent");
|
|
530
|
+
if (typeof dispatchEvent === "function") {
|
|
531
|
+
Reflect.apply(dispatchEvent, element, [new Event("input", { bubbles: true })]);
|
|
532
|
+
Reflect.apply(dispatchEvent, element, [new Event("change", { bubbles: true })]);
|
|
533
|
+
}
|
|
534
|
+
}, value)),
|
|
535
|
+
click: (selector, signal, onDispatch) => runObservedPageAction(page, selector, signal, onDispatch, (element) => element.click()),
|
|
163
536
|
screenshot: (fullPage) => page.screenshot({
|
|
164
537
|
type: "png",
|
|
165
538
|
fullPage
|
|
@@ -172,7 +545,8 @@ const makeProductionPage = (page) => {
|
|
|
172
545
|
behavior: "instant"
|
|
173
546
|
}]);
|
|
174
547
|
}, deltaX, deltaY),
|
|
175
|
-
createCdpSession: async () => makeProductionCdpSession(await page.createCDPSession())
|
|
548
|
+
createCdpSession: async () => makeProductionCdpSession(await page.createCDPSession()),
|
|
549
|
+
setViewport: (viewport) => page.setViewport(viewport)
|
|
176
550
|
};
|
|
177
551
|
};
|
|
178
552
|
const makeProductionContext = (context) => ({
|
|
@@ -202,6 +576,13 @@ const actionError = (operation, cause) => InteractiveBrowserActionError.make({
|
|
|
202
576
|
message: `The interactive browser ${operation} operation failed`,
|
|
203
577
|
...cause === void 0 ? {} : { cause }
|
|
204
578
|
});
|
|
579
|
+
const undispatchedActionError = (operation) => InteractiveBrowserActionError.make({
|
|
580
|
+
implementation: browserRunInteractiveImplementation,
|
|
581
|
+
operation,
|
|
582
|
+
message: `The interactive browser ${operation} operation was not dispatched`
|
|
583
|
+
});
|
|
584
|
+
/** Recognizes a local pre-dispatch refusal without exposing selector or page content. */
|
|
585
|
+
const isBrowserRunUndispatchedActionError = (error) => Schema.is(InteractiveBrowserActionError)(error) && error.implementation.identity === browserRunInteractiveImplementation.identity && (error.operation === "click" || error.operation === "fill") && error.message === `The interactive browser ${error.operation} operation was not dispatched`;
|
|
205
586
|
const policyError = (message) => InteractiveBrowserPolicyDeniedError.make({
|
|
206
587
|
implementation: browserRunInteractiveImplementation,
|
|
207
588
|
message
|
|
@@ -282,6 +663,12 @@ const stateFailure = (state) => {
|
|
|
282
663
|
if (state.violation.value !== void 0) return state.violation.value;
|
|
283
664
|
if (state.closed.value || state.disconnected.value || state.uncertain.value) return expiredError();
|
|
284
665
|
};
|
|
666
|
+
var BrowserRunRemoteFailure = class {
|
|
667
|
+
cause;
|
|
668
|
+
constructor(cause) {
|
|
669
|
+
this.cause = cause;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
285
672
|
const awaitPendingRequests = (state) => Effect.suspend(() => {
|
|
286
673
|
const pending = [...state.pendingRequests];
|
|
287
674
|
return pending.length === 0 ? Effect.void : Effect.promise(() => Promise.all(pending)).pipe(Effect.asVoid, Effect.andThen(awaitPendingRequests(state)));
|
|
@@ -331,32 +718,87 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (page
|
|
|
331
718
|
const actions = yield* Ref.make(0);
|
|
332
719
|
const remote = (operation, evaluate) => Effect.tryPromise({
|
|
333
720
|
try: evaluate,
|
|
334
|
-
catch: (cause) =>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
721
|
+
catch: (cause) => new BrowserRunRemoteFailure(cause)
|
|
722
|
+
}).pipe(Effect.catch((failure) => {
|
|
723
|
+
const cause = failure.cause;
|
|
724
|
+
if (cause instanceof BrowserRunActionUndispatched) return Effect.logInfo("Browser interactive action was not dispatched").pipe(Effect.annotateLogs({
|
|
725
|
+
"browser.action": operation,
|
|
726
|
+
"browser.selector_match_count": cause.matchCount
|
|
727
|
+
}), Effect.andThen(Effect.fail(undispatchedActionError(operation))));
|
|
728
|
+
if (state.disconnected.value || isRemoteClosure(cause)) {
|
|
729
|
+
state.disconnected.value = true;
|
|
730
|
+
return Effect.fail(expiredError());
|
|
340
731
|
}
|
|
732
|
+
return Effect.fail(actionError(operation, cause));
|
|
733
|
+
}));
|
|
734
|
+
const observedAction = (operation, evaluate) => Effect.suspend(() => {
|
|
735
|
+
let dispatched = false;
|
|
736
|
+
let pending;
|
|
737
|
+
return remote(operation, (signal) => {
|
|
738
|
+
pending = evaluate(signal, () => {
|
|
739
|
+
dispatched = true;
|
|
740
|
+
});
|
|
741
|
+
return pending;
|
|
742
|
+
}).pipe(Effect.onInterrupt(() => Effect.gen(function* () {
|
|
743
|
+
state.uncertain.value = true;
|
|
744
|
+
yield* Effect.logWarning("Browser interactive action interrupted").pipe(Effect.annotateLogs({
|
|
745
|
+
"browser.action": operation,
|
|
746
|
+
"browser.action_dispatched": dispatched,
|
|
747
|
+
"browser.action_outcome_unknown": dispatched
|
|
748
|
+
}));
|
|
749
|
+
const completion = pending;
|
|
750
|
+
if (completion !== void 0) yield* Effect.promise(() => boundedBestEffort(completion, 500));
|
|
751
|
+
})));
|
|
752
|
+
});
|
|
753
|
+
const decodeActionObservation = Effect.fn("BrowserRunInteractive.decodeActionObservation")(function* (raw) {
|
|
754
|
+
return yield* Schema.decodeUnknownEffect(ActionObservation)(raw).pipe(Effect.mapError((cause) => protocolError("The browser returned a malformed action observation", cause)));
|
|
341
755
|
});
|
|
342
|
-
const
|
|
756
|
+
const logActionObservation = (operation, observation) => Effect.logInfo("Browser interactive action observed").pipe(Effect.annotateLogs({
|
|
757
|
+
"browser.action": operation,
|
|
758
|
+
"browser.selector_match_count": observation.before.matchCount,
|
|
759
|
+
...observation.before.kind === void 0 ? {} : { "browser.target_kind": observation.before.kind },
|
|
760
|
+
...observation.before.checked === void 0 ? {} : { "browser.target_checked_before": observation.before.checked },
|
|
761
|
+
...observation.after?.checked === void 0 ? {} : { "browser.target_checked_after": observation.after.checked },
|
|
762
|
+
...observation.before.selected === void 0 ? {} : { "browser.target_selected_before": observation.before.selected },
|
|
763
|
+
...observation.after?.selected === void 0 ? {} : { "browser.target_selected_after": observation.after.selected },
|
|
764
|
+
...observation.before.disabled === void 0 ? {} : { "browser.target_disabled_before": observation.before.disabled },
|
|
765
|
+
...observation.after?.disabled === void 0 ? {} : { "browser.target_disabled_after": observation.after.disabled },
|
|
766
|
+
...observation.before.required === void 0 ? {} : { "browser.target_required_before": observation.before.required },
|
|
767
|
+
...observation.after?.required === void 0 ? {} : { "browser.target_required_after": observation.after.required },
|
|
768
|
+
...observation.before.valid === void 0 ? {} : { "browser.target_valid_before": observation.before.valid },
|
|
769
|
+
...observation.after?.valid === void 0 ? {} : { "browser.target_valid_after": observation.after.valid },
|
|
770
|
+
...observation.before.formValid === void 0 ? {} : { "browser.form_valid_before": observation.before.formValid },
|
|
771
|
+
...observation.after?.formValid === void 0 ? {} : { "browser.form_valid_after": observation.after.formValid },
|
|
772
|
+
"browser.target_after_unavailable": observation.afterUnavailable,
|
|
773
|
+
"browser.fetch_xhr_total": observation.network.total,
|
|
774
|
+
"browser.fetch_xhr_2xx": observation.network.status2xx,
|
|
775
|
+
"browser.fetch_xhr_3xx": observation.network.status3xx,
|
|
776
|
+
"browser.fetch_xhr_4xx": observation.network.status4xx,
|
|
777
|
+
"browser.fetch_xhr_5xx": observation.network.status5xx,
|
|
778
|
+
"browser.fetch_xhr_failed": observation.network.failed,
|
|
779
|
+
"browser.fetch_xhr_pending": observation.network.pending,
|
|
780
|
+
"browser.network_settle_timed_out": observation.network.settleTimedOut
|
|
781
|
+
}));
|
|
782
|
+
const run = (effect, preflight = Effect.void, consumeAction = true) => permits.withPermitsIfAvailable(1)(Effect.gen(function* () {
|
|
343
783
|
const unavailable = stateFailure(state);
|
|
344
784
|
if (unavailable !== void 0) return yield* unavailable;
|
|
345
785
|
yield* preflight;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
786
|
+
if (consumeAction) {
|
|
787
|
+
const admitted = yield* Ref.modify(actions, (count) => count >= policy.maxActions ? [{
|
|
788
|
+
allowed: false,
|
|
789
|
+
observed: count + 1
|
|
790
|
+
}, count] : [{
|
|
791
|
+
allowed: true,
|
|
792
|
+
observed: count + 1
|
|
793
|
+
}, count + 1]);
|
|
794
|
+
if (!admitted.allowed) return yield* InteractiveBrowserLimitError.make({
|
|
795
|
+
implementation: browserRunInteractiveImplementation,
|
|
796
|
+
limit: "actions",
|
|
797
|
+
maximum: policy.maxActions,
|
|
798
|
+
observed: admitted.observed,
|
|
799
|
+
message: "The browser action limit was reached"
|
|
800
|
+
});
|
|
801
|
+
}
|
|
360
802
|
const completed = effect.pipe(Effect.catch((error) => {
|
|
361
803
|
const failure = stateFailure(state);
|
|
362
804
|
return Effect.fail(failure ?? error);
|
|
@@ -371,6 +813,7 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (page
|
|
|
371
813
|
})), Effect.catch((error) => {
|
|
372
814
|
if (Schema.is(InteractiveBrowserLimitError)(error) && error.limit === "elapsed") return Effect.fail(error);
|
|
373
815
|
const failure = stateFailure(state);
|
|
816
|
+
if (Schema.is(InteractiveBrowserActionError)(error) && !isBrowserRunUndispatchedActionError(error)) state.uncertain.value = true;
|
|
374
817
|
return Effect.fail(failure ?? error);
|
|
375
818
|
}));
|
|
376
819
|
})).pipe(Effect.flatMap((result) => Option.isSome(result) ? Effect.succeed(result.value) : Effect.fail(InteractiveBrowserBusyError.make({
|
|
@@ -404,8 +847,16 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (page
|
|
|
404
847
|
});
|
|
405
848
|
return yield* Schema.decodeUnknownEffect(BrowserTextResult)({ text: observation.text }).pipe(Effect.mapError((cause) => protocolError("The browser returned malformed page text", cause)));
|
|
406
849
|
})),
|
|
407
|
-
fill: (request) => run(
|
|
408
|
-
|
|
850
|
+
fill: (request) => run(Effect.gen(function* () {
|
|
851
|
+
const observation = yield* observedAction("fill", (signal, onDispatch) => page.fill(request.selector, request.value, signal, onDispatch)).pipe(Effect.flatMap(decodeActionObservation));
|
|
852
|
+
yield* logActionObservation("fill", observation);
|
|
853
|
+
return yield* decodeActionResult(page, policy);
|
|
854
|
+
})),
|
|
855
|
+
click: (request) => run(Effect.gen(function* () {
|
|
856
|
+
const observation = yield* observedAction("click", (signal, onDispatch) => page.click(request.selector, signal, onDispatch)).pipe(Effect.flatMap(decodeActionObservation));
|
|
857
|
+
yield* logActionObservation("click", observation);
|
|
858
|
+
return yield* decodeActionResult(page, policy);
|
|
859
|
+
})),
|
|
409
860
|
screenshot: (request) => Schema.decodeUnknownEffect(BrowserScreenshotRequest)(request).pipe(Effect.mapError(() => policyError("The browser screenshot request is malformed")), Effect.flatMap((decoded) => run(Effect.gen(function* () {
|
|
410
861
|
const raw = yield* remote("screenshot", () => page.screenshot(decoded.fullPage));
|
|
411
862
|
const bytes = yield* Schema.decodeUnknownEffect(PngBytes)(raw).pipe(Effect.mapError(() => protocolError("The browser returned a malformed PNG screenshot")));
|
|
@@ -561,6 +1012,16 @@ const makeHostService = (binding) => {
|
|
|
561
1012
|
return {
|
|
562
1013
|
handle: runtime.handle,
|
|
563
1014
|
sessionId: Redacted.make(sessionIdValue),
|
|
1015
|
+
resizeViewport: (viewport) => decodeViewport(viewport).pipe(Effect.flatMap((decoded) => runtime.run(Effect.tryPromise({
|
|
1016
|
+
try: () => page.setViewport(decoded),
|
|
1017
|
+
catch: (cause) => {
|
|
1018
|
+
if (state.disconnected.value || isRemoteClosure(cause)) {
|
|
1019
|
+
state.disconnected.value = true;
|
|
1020
|
+
return expiredError();
|
|
1021
|
+
}
|
|
1022
|
+
return protocolError("Resizing the browser viewport failed", cause);
|
|
1023
|
+
}
|
|
1024
|
+
}), currentPagePreflight, false))),
|
|
564
1025
|
getLiveView: (request) => Schema.decodeUnknownEffect(BrowserRunLiveViewRequest)(request).pipe(Effect.mapError(() => policyError("The Live View request is malformed")), Effect.flatMap((decoded) => runtime.run(cdpCommand(page, state, "Cloudflare.getLiveView", {
|
|
565
1026
|
mode: decoded.mode,
|
|
566
1027
|
expiresInMs: decoded.expiresInMs
|
|
@@ -613,6 +1074,6 @@ const browserRunInteractiveLayer = () => Layer.effect(InteractiveBrowser, Effect
|
|
|
613
1074
|
return InteractiveBrowser.of({ open: (policy) => host.open(policy).pipe(Effect.map((session) => session.handle)) });
|
|
614
1075
|
}));
|
|
615
1076
|
//#endregion
|
|
616
|
-
export { BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunInteractiveBinding, BrowserRunInteractiveHost, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, browserRunInteractiveHostLayer, browserRunInteractiveImplementation, browserRunInteractiveLayer };
|
|
1077
|
+
export { BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunInteractiveBinding, BrowserRunInteractiveHost, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, BrowserRunViewport, browserRunInteractiveHostLayer, browserRunInteractiveImplementation, browserRunInteractiveLayer, isBrowserRunUndispatchedActionError };
|
|
617
1078
|
|
|
618
1079
|
//# sourceMappingURL=interactive-browser.mjs.map
|