1// @effect-diagnostics globalFetch:off - This browser transport runs without an Effect runtime, like the live stream.
2import { withDeviceHubQuery } from "./hubAccess.ts";
3import type { DeviceStreamTarget } from "./stream.ts";
4
5export class DeviceScreenshotError extends Error {
6 readonly status: number;
7
8 constructor(status: number) {
9 super(`Screenshot capture failed (${status}). Try again.`);
10 this.status = status;
11 }
12}
13
14/** Captures native pixels through the same host and credentials as the live stream, in either presentation. */
15export async function captureDeviceScreenshot(target: DeviceStreamTarget, signal: AbortSignal) {
16 const vendor = target.platform === "ios" ? "serve-sim" : "serve-emu";
17 const url = withDeviceHubQuery(
18 `${target.access.httpBase}/vendor/${vendor}/api/screenshot?device=${encodeURIComponent(target.deviceId)}`,
19 target.access,
20 );
21 const response = await fetch(url, {
22 method: "POST",
23 credentials: target.access.credentials ? "include" : "omit",
24 signal: AbortSignal.any([signal, AbortSignal.timeout(20_000)]),
25 });
26 if (!response.ok) throw new DeviceScreenshotError(response.status);
27 const image = await response.blob();
28 if (!image.size) throw new Error("The device returned an empty screenshot. Try again.");
29 return image;
30}