| 1 | import type { DeviceHubAccess } from "@t3tools/client-runtime/state/deviceHubAccess"; |
| 2 | import { withDeviceHubQuery } from "@t3tools/client-runtime/state/deviceHubAccess"; |
| 3 | |
| 4 | export type AndroidFoldPosture = "closed" | "opened"; |
| 5 | |
| 6 | export interface AndroidFoldState { |
| 7 | readonly supported: boolean; |
| 8 | readonly posture: "closed" | "half_opened" | "opened" | "flipped" | "tent" | null; |
| 9 | readonly hingeAngle: number | null; |
| 10 | } |
| 11 | |
| 12 | const isRecord = (value: unknown): value is Record<string, unknown> => |
| 13 | typeof value === "object" && value !== null && !Array.isArray(value); |
| 14 | |
| 15 | const parseFold = (payload: unknown): AndroidFoldState => { |
| 16 | if (!isRecord(payload) || payload.ok !== true || !isRecord(payload.fold)) { |
| 17 | throw new Error("Unexpected Android fold response."); |
| 18 | } |
| 19 | const { supported, posture, hingeAngle } = payload.fold; |
| 20 | if ( |
| 21 | typeof supported !== "boolean" || |
| 22 | (posture !== null && |
| 23 | posture !== "closed" && |
| 24 | posture !== "half_opened" && |
| 25 | posture !== "opened" && |
| 26 | posture !== "flipped" && |
| 27 | posture !== "tent") || |
| 28 | (hingeAngle !== null && (typeof hingeAngle !== "number" || !Number.isFinite(hingeAngle))) |
| 29 | ) { |
| 30 | throw new Error("Unexpected Android fold response."); |
| 31 | } |
| 32 | return { supported, posture, hingeAngle }; |
| 33 | }; |
| 34 | |
| 35 | const foldRequest = async ( |
| 36 | access: DeviceHubAccess, |
| 37 | deviceId: string, |
| 38 | posture?: AndroidFoldPosture, |
| 39 | signal?: AbortSignal, |
| 40 | ): Promise<AndroidFoldState> => { |
| 41 | const url = withDeviceHubQuery( |
| 42 | `${access.httpBase}/vendor/serve-emu/api/fold?${new URLSearchParams({ device: deviceId })}`, |
| 43 | access, |
| 44 | ); |
| 45 | const response = await fetch(url, { |
| 46 | method: posture ? "POST" : "GET", |
| 47 | cache: "no-store", |
| 48 | credentials: access.credentials ? "include" : "same-origin", |
| 49 | ...(posture |
| 50 | ? { headers: { "content-type": "application/json" }, body: JSON.stringify({ posture }) } |
| 51 | : {}), |
| 52 | ...(signal ? { signal } : {}), |
| 53 | }); |
| 54 | if (!response.ok) { |
| 55 | const payload: unknown = await response.json().catch(() => null); |
| 56 | const error = isRecord(payload) ? payload.error : null; |
| 57 | throw new Error( |
| 58 | typeof error === "string" ? error : `Fold command failed (${response.status}).`, |
| 59 | ); |
| 60 | } |
| 61 | return parseFold(await response.json()); |
| 62 | }; |
| 63 | |
| 64 | export const readAndroidFold = (access: DeviceHubAccess, deviceId: string, signal?: AbortSignal) => |
| 65 | foldRequest(access, deviceId, undefined, signal); |
| 66 | |
| 67 | export const setAndroidFold = ( |
| 68 | access: DeviceHubAccess, |
| 69 | deviceId: string, |
| 70 | posture: AndroidFoldPosture, |
| 71 | signal?: AbortSignal, |
| 72 | ) => foldRequest(access, deviceId, posture, signal); |