Skip to content
Source
Sign inSign up

deviceFold.ts

79 lines2.7 KB

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