| 1 | // @effect-diagnostics globalFetch:off - This browser transport runs without an Effect runtime, like the live stream. |
| 2 | import { withDeviceHubQuery } from "./hubAccess.ts"; |
| 3 | import type { DeviceStreamTarget } from "./stream.ts"; |
| 4 | |
| 5 | export 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. */ |
| 15 | export 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 | } |