@expo/hub-client 1.3.0 → 1.3.1

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/README.md CHANGED
@@ -230,6 +230,10 @@ The stream and input URLs then use that server. Start serve-sim with
230
230
  `--cors-origin http://localhost:8081`. Without it, the exec-ws socket closes, and logs,
231
231
  events, metrics and UI requests stop, even when both servers run on `localhost`.
232
232
 
233
+ The foreground app icon does not need exec-ws. hub-client reads it from serve-sim's
234
+ `/api/apps/icon` route, a plain GET, when `/api` advertises the route. Older servers send the
235
+ icon over exec-ws.
236
+
233
237
  A serve-sim started with `--require-token`, such as an EAS Simulator Preview session, needs
234
238
  its session token on every request. Pass it as `options.token`:
235
239
 
@@ -1,6 +1,6 @@
1
1
  import { type ForegroundApp } from './types';
2
2
  type FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;
3
- export declare function parseAndroidAppIcon(payload: unknown): string | null;
3
+ export declare function parseAppIconResponse(payload: unknown): string | null;
4
4
  export declare function fetchAndroidAppIcon(baseUrl: string, device: string | null, packageName: string, fetchImpl?: FetchImpl): Promise<string | null>;
5
5
  /**
6
6
  * The foreground poll reports no icon, so a changed field would otherwise drop
@@ -1 +1 @@
1
- {"version":3,"file":"android-app-icon.d.ts","sourceRoot":"","sources":["../src/android-app-icon.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAK1E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYnE;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,IAAI,EAAE,aAAa,GAAG,aAAa,CAIlG"}
1
+ {"version":3,"file":"android-app-icon.d.ts","sourceRoot":"","sources":["../src/android-app-icon.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAK1E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYpE;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,IAAI,EAAE,aAAa,GAAG,aAAa,CAIlG"}
@@ -1,12 +1,12 @@
1
1
  import { deviceApiUrl } from "./android-api-url.mjs";
2
2
  import { asRecord } from "./app-permissions.mjs";
3
- /** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. */ const APP_ICON_MIME_TYPES = [
3
+ /** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. serve-sim's route answers the same shape. */ const APP_ICON_MIME_TYPES = [
4
4
  'image/png',
5
5
  'image/webp',
6
6
  'image/jpeg',
7
7
  'image/gif'
8
8
  ];
9
- export function parseAndroidAppIcon(payload) {
9
+ export function parseAppIconResponse(payload) {
10
10
  const body = asRecord(payload);
11
11
  if (!body || body.ok !== true) throw new Error('app icon response is invalid');
12
12
  if (body.icon === null) return null;
@@ -26,7 +26,7 @@ export async function fetchAndroidAppIcon(baseUrl, device, packageName, fetchImp
26
26
  cache: 'no-store'
27
27
  });
28
28
  if (!res.ok) throw new Error(`app icon request failed with ${res.status}`);
29
- return parseAndroidAppIcon(await res.json());
29
+ return parseAppIconResponse(await res.json());
30
30
  }
31
31
  /**
32
32
  * The foreground poll reports no icon, so a changed field would otherwise drop
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/android-app-icon.ts"],"sourcesContent":["import { deviceApiUrl } from './android-api-url';\nimport { asRecord } from './app-permissions';\nimport { type ForegroundApp } from './types';\n\ntype FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;\n\n/** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. */\nconst APP_ICON_MIME_TYPES = ['image/png', 'image/webp', 'image/jpeg', 'image/gif'];\n\nexport function parseAndroidAppIcon(payload: unknown): string | null {\n const body = asRecord(payload);\n if (!body || body.ok !== true) throw new Error('app icon response is invalid');\n if (body.icon === null) return null;\n const icon = asRecord(body.icon);\n if (!icon) throw new Error('app icon must be an object');\n const { mimeType, data } = icon;\n if (typeof mimeType !== 'string' || !APP_ICON_MIME_TYPES.includes(mimeType)) {\n throw new Error('app icon mimeType is invalid');\n }\n if (typeof data !== 'string' || !data) throw new Error('app icon data is invalid');\n return `data:${mimeType};base64,${data}`;\n}\n\nexport async function fetchAndroidAppIcon(\n baseUrl: string,\n device: string | null,\n packageName: string,\n fetchImpl: FetchImpl = fetch,\n): Promise<string | null> {\n const url = new URL(deviceApiUrl(baseUrl, '/api/apps/icon', device));\n url.searchParams.set('packageName', packageName);\n const res = await fetchImpl(url.toString(), { cache: 'no-store' });\n if (!res.ok) throw new Error(`app icon request failed with ${res.status}`);\n return parseAndroidAppIcon(await res.json());\n}\n\n/**\n * The foreground poll reports no icon, so a changed field would otherwise drop\n * the resolved one and the effect would not refetch for the same package.\n */\nexport function carryForwardAppIcon(prev: ForegroundApp | null, next: ForegroundApp): ForegroundApp {\n return prev && prev.id === next.id && prev.iconDataUrl && !next.iconDataUrl\n ? { ...next, iconDataUrl: prev.iconDataUrl }\n : next;\n}\n"],"names":["deviceApiUrl","asRecord","APP_ICON_MIME_TYPES","parseAndroidAppIcon","payload","body","ok","Error","icon","mimeType","data","includes","fetchAndroidAppIcon","baseUrl","device","packageName","fetchImpl","fetch","url","URL","searchParams","set","res","toString","cache","status","json","carryForwardAppIcon","prev","next","id","iconDataUrl"],"mappings":"AAAA,SAASA,YAAY,QAAQ,wBAAoB;AACjD,SAASC,QAAQ,QAAQ,wBAAoB;AAK7C,gEAAgE,GAChE,MAAMC,sBAAsB;IAAC;IAAa;IAAc;IAAc;CAAY;AAElF,OAAO,SAASC,oBAAoBC,OAAgB;IAClD,MAAMC,OAAOJ,SAASG;IACtB,IAAI,CAACC,QAAQA,KAAKC,EAAE,KAAK,MAAM,MAAM,IAAIC,MAAM;IAC/C,IAAIF,KAAKG,IAAI,KAAK,MAAM,OAAO;IAC/B,MAAMA,OAAOP,SAASI,KAAKG,IAAI;IAC/B,IAAI,CAACA,MAAM,MAAM,IAAID,MAAM;IAC3B,MAAM,EAAEE,QAAQ,EAAEC,IAAI,EAAE,GAAGF;IAC3B,IAAI,OAAOC,aAAa,YAAY,CAACP,oBAAoBS,QAAQ,CAACF,WAAW;QAC3E,MAAM,IAAIF,MAAM;IAClB;IACA,IAAI,OAAOG,SAAS,YAAY,CAACA,MAAM,MAAM,IAAIH,MAAM;IACvD,OAAO,CAAC,KAAK,EAAEE,SAAS,QAAQ,EAAEC,MAAM;AAC1C;AAEA,OAAO,eAAeE,oBACpBC,OAAe,EACfC,MAAqB,EACrBC,WAAmB,EACnBC,YAAuBC,KAAK;IAE5B,MAAMC,MAAM,IAAIC,IAAInB,aAAaa,SAAS,kBAAkBC;IAC5DI,IAAIE,YAAY,CAACC,GAAG,CAAC,eAAeN;IACpC,MAAMO,MAAM,MAAMN,UAAUE,IAAIK,QAAQ,IAAI;QAAEC,OAAO;IAAW;IAChE,IAAI,CAACF,IAAIhB,EAAE,EAAE,MAAM,IAAIC,MAAM,CAAC,6BAA6B,EAAEe,IAAIG,MAAM,EAAE;IACzE,OAAOtB,oBAAoB,MAAMmB,IAAII,IAAI;AAC3C;AAEA;;;CAGC,GACD,OAAO,SAASC,oBAAoBC,IAA0B,EAAEC,IAAmB;IACjF,OAAOD,QAAQA,KAAKE,EAAE,KAAKD,KAAKC,EAAE,IAAIF,KAAKG,WAAW,IAAI,CAACF,KAAKE,WAAW,GACvE;QAAE,GAAGF,IAAI;QAAEE,aAAaH,KAAKG,WAAW;IAAC,IACzCF;AACN"}
1
+ {"version":3,"sources":["../src/android-app-icon.ts"],"sourcesContent":["import { deviceApiUrl } from './android-api-url';\nimport { asRecord } from './app-permissions';\nimport { type ForegroundApp } from './types';\n\ntype FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;\n\n/** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. serve-sim's route answers the same shape. */\nconst APP_ICON_MIME_TYPES = ['image/png', 'image/webp', 'image/jpeg', 'image/gif'];\n\nexport function parseAppIconResponse(payload: unknown): string | null {\n const body = asRecord(payload);\n if (!body || body.ok !== true) throw new Error('app icon response is invalid');\n if (body.icon === null) return null;\n const icon = asRecord(body.icon);\n if (!icon) throw new Error('app icon must be an object');\n const { mimeType, data } = icon;\n if (typeof mimeType !== 'string' || !APP_ICON_MIME_TYPES.includes(mimeType)) {\n throw new Error('app icon mimeType is invalid');\n }\n if (typeof data !== 'string' || !data) throw new Error('app icon data is invalid');\n return `data:${mimeType};base64,${data}`;\n}\n\nexport async function fetchAndroidAppIcon(\n baseUrl: string,\n device: string | null,\n packageName: string,\n fetchImpl: FetchImpl = fetch,\n): Promise<string | null> {\n const url = new URL(deviceApiUrl(baseUrl, '/api/apps/icon', device));\n url.searchParams.set('packageName', packageName);\n const res = await fetchImpl(url.toString(), { cache: 'no-store' });\n if (!res.ok) throw new Error(`app icon request failed with ${res.status}`);\n return parseAppIconResponse(await res.json());\n}\n\n/**\n * The foreground poll reports no icon, so a changed field would otherwise drop\n * the resolved one and the effect would not refetch for the same package.\n */\nexport function carryForwardAppIcon(prev: ForegroundApp | null, next: ForegroundApp): ForegroundApp {\n return prev && prev.id === next.id && prev.iconDataUrl && !next.iconDataUrl\n ? { ...next, iconDataUrl: prev.iconDataUrl }\n : next;\n}\n"],"names":["deviceApiUrl","asRecord","APP_ICON_MIME_TYPES","parseAppIconResponse","payload","body","ok","Error","icon","mimeType","data","includes","fetchAndroidAppIcon","baseUrl","device","packageName","fetchImpl","fetch","url","URL","searchParams","set","res","toString","cache","status","json","carryForwardAppIcon","prev","next","id","iconDataUrl"],"mappings":"AAAA,SAASA,YAAY,QAAQ,wBAAoB;AACjD,SAASC,QAAQ,QAAQ,wBAAoB;AAK7C,0GAA0G,GAC1G,MAAMC,sBAAsB;IAAC;IAAa;IAAc;IAAc;CAAY;AAElF,OAAO,SAASC,qBAAqBC,OAAgB;IACnD,MAAMC,OAAOJ,SAASG;IACtB,IAAI,CAACC,QAAQA,KAAKC,EAAE,KAAK,MAAM,MAAM,IAAIC,MAAM;IAC/C,IAAIF,KAAKG,IAAI,KAAK,MAAM,OAAO;IAC/B,MAAMA,OAAOP,SAASI,KAAKG,IAAI;IAC/B,IAAI,CAACA,MAAM,MAAM,IAAID,MAAM;IAC3B,MAAM,EAAEE,QAAQ,EAAEC,IAAI,EAAE,GAAGF;IAC3B,IAAI,OAAOC,aAAa,YAAY,CAACP,oBAAoBS,QAAQ,CAACF,WAAW;QAC3E,MAAM,IAAIF,MAAM;IAClB;IACA,IAAI,OAAOG,SAAS,YAAY,CAACA,MAAM,MAAM,IAAIH,MAAM;IACvD,OAAO,CAAC,KAAK,EAAEE,SAAS,QAAQ,EAAEC,MAAM;AAC1C;AAEA,OAAO,eAAeE,oBACpBC,OAAe,EACfC,MAAqB,EACrBC,WAAmB,EACnBC,YAAuBC,KAAK;IAE5B,MAAMC,MAAM,IAAIC,IAAInB,aAAaa,SAAS,kBAAkBC;IAC5DI,IAAIE,YAAY,CAACC,GAAG,CAAC,eAAeN;IACpC,MAAMO,MAAM,MAAMN,UAAUE,IAAIK,QAAQ,IAAI;QAAEC,OAAO;IAAW;IAChE,IAAI,CAACF,IAAIhB,EAAE,EAAE,MAAM,IAAIC,MAAM,CAAC,6BAA6B,EAAEe,IAAIG,MAAM,EAAE;IACzE,OAAOtB,qBAAqB,MAAMmB,IAAII,IAAI;AAC5C;AAEA;;;CAGC,GACD,OAAO,SAASC,oBAAoBC,IAA0B,EAAEC,IAAmB;IACjF,OAAOD,QAAQA,KAAKE,EAAE,KAAKD,KAAKC,EAAE,IAAIF,KAAKG,WAAW,IAAI,CAACF,KAAKE,WAAW,GACvE;QAAE,GAAGF,IAAI;QAAEE,aAAaH,KAAKG,WAAW;IAAC,IACzCF;AACN"}
@@ -17,7 +17,29 @@ export type IosAppDetails = Pick<ForegroundApp, 'label' | 'version' | 'build' |
17
17
  * bundle can't be located (e.g. the process is not a plain app — SpringBoard
18
18
  * has no user-visible container on some runtimes). Icons compiled solely into
19
19
  * Assets.car yield no `iconDataUrl`; callers should fall back to a placeholder.
20
+ * With `includeIcon: false` the icon is left to {@link fetchIosAppIcon} and the
21
+ * result carries no `iconDataUrl` key.
20
22
  */
21
- export declare function fetchIosAppDetails(run: RunHostAction, udid: string, bundleId: string): Promise<IosAppDetails | null>;
22
- export declare function getIosAppDetails(run: RunHostAction, udid: string, bundleId: string): Promise<IosAppDetails | null>;
23
+ export declare function fetchIosAppDetails(run: RunHostAction, udid: string, bundleId: string, { includeIcon }?: {
24
+ includeIcon?: boolean;
25
+ }): Promise<IosAppDetails | null>;
26
+ type FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;
27
+ /**
28
+ * The app's icon from serve-sim's `/api/apps/icon` route (`appIconEndpoint` in
29
+ * `/api`): one plain GET, so it also reaches a tunneled server whose exec-ws
30
+ * socket the page cannot open. Null when the app has no loose icon PNG.
31
+ *
32
+ * Not cached: the route reads the installed bundle on each request, so a
33
+ * reinstalled build shows its new icon. A network error or a 5xx (the route
34
+ * answers 503 while simctl fails) is retried a few times. A 4xx is not,
35
+ * because the same request gets the same answer.
36
+ */
37
+ export declare function fetchIosAppIcon(appIconUrl: string, bundleId: string, { fetchImpl, signal, retryDelaysMs, }?: {
38
+ fetchImpl?: FetchImpl;
39
+ signal?: AbortSignal;
40
+ retryDelaysMs?: readonly number[];
41
+ }): Promise<string | null>;
42
+ export declare function getIosAppDetails(run: RunHostAction, udid: string, bundleId: string, options?: {
43
+ includeIcon?: boolean;
44
+ }): Promise<IosAppDetails | null>;
23
45
  //# sourceMappingURL=ios-app-details.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ios-app-details.d.ts","sourceRoot":"","sources":["../src/ios-app-details.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;GAMG;AAEH,YAAY,EAAE,gBAAgB,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/E,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,aAAa,EACb,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,CACnF,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAoD/B;AAQD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU/B"}
1
+ {"version":3,"file":"ios-app-details.d.ts","sourceRoot":"","sources":["../src/ios-app-details.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;GAMG;AAEH,YAAY,EAAE,gBAAgB,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/E,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,aAAa,EACb,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,CACnF,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,EAAE,WAAkB,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAoD/B;AAED,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAK1E;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,EACE,SAAiB,EACjB,MAAM,EACN,aAAoC,GACrC,GAAE;IAAE,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAO,GACzF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBxB;AAuBD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU/B"}
@@ -1,3 +1,4 @@
1
+ import { parseAppIconResponse } from "./android-app-icon.mjs";
1
2
  /**
2
3
  * Resolve display name, versions, and icon for an installed app:
3
4
  * `app.container` (simctl get_app_container) → `app.infoPlist` (plutil JSON) →
@@ -5,7 +6,9 @@
5
6
  * bundle can't be located (e.g. the process is not a plain app — SpringBoard
6
7
  * has no user-visible container on some runtimes). Icons compiled solely into
7
8
  * Assets.car yield no `iconDataUrl`; callers should fall back to a placeholder.
8
- */ export async function fetchIosAppDetails(run, udid, bundleId) {
9
+ * With `includeIcon: false` the icon is left to {@link fetchIosAppIcon} and the
10
+ * result carries no `iconDataUrl` key.
11
+ */ export async function fetchIosAppDetails(run, udid, bundleId, { includeIcon = true } = {}) {
9
12
  const ctn = await run('app.container', {
10
13
  udid,
11
14
  bundleId
@@ -30,7 +33,7 @@
30
33
  if (iconFiles && iconFiles.length > 0) iconName = iconFiles[iconFiles.length - 1];
31
34
  else if (typeof info?.CFBundleIconFile === 'string') iconName = info.CFBundleIconFile;
32
35
  let iconDataUrl;
33
- if (iconName) {
36
+ if (includeIcon && iconName) {
34
37
  // Loose PNGs commonly sit next to Assets.car under a handful of names.
35
38
  const candidates = [
36
39
  `${iconName}@3x.png`,
@@ -60,19 +63,74 @@
60
63
  build: info.CFBundleVersion,
61
64
  minOS: info.MinimumOSVersion,
62
65
  executable: info.CFBundleExecutable,
63
- iconDataUrl
66
+ ...includeIcon ? {
67
+ iconDataUrl
68
+ } : {}
64
69
  };
65
70
  }
71
+ /** Waits before each retry of the icon request. */ const ICON_RETRY_DELAYS_MS = [
72
+ 1_000,
73
+ 2_000,
74
+ 4_000
75
+ ];
76
+ /**
77
+ * The app's icon from serve-sim's `/api/apps/icon` route (`appIconEndpoint` in
78
+ * `/api`): one plain GET, so it also reaches a tunneled server whose exec-ws
79
+ * socket the page cannot open. Null when the app has no loose icon PNG.
80
+ *
81
+ * Not cached: the route reads the installed bundle on each request, so a
82
+ * reinstalled build shows its new icon. A network error or a 5xx (the route
83
+ * answers 503 while simctl fails) is retried a few times. A 4xx is not,
84
+ * because the same request gets the same answer.
85
+ */ export async function fetchIosAppIcon(appIconUrl, bundleId, { fetchImpl = fetch, signal, retryDelaysMs = ICON_RETRY_DELAYS_MS } = {}) {
86
+ const url = new URL(appIconUrl);
87
+ url.searchParams.set('bundleId', bundleId);
88
+ for(let attempt = 0;; attempt++){
89
+ const canRetry = attempt < retryDelaysMs.length;
90
+ let res;
91
+ try {
92
+ res = await fetchImpl(url.toString(), {
93
+ cache: 'no-store',
94
+ signal
95
+ });
96
+ } catch (err) {
97
+ if (signal?.aborted || !canRetry) throw err;
98
+ await delay(retryDelaysMs[attempt], signal);
99
+ continue;
100
+ }
101
+ if (res.ok) return parseAppIconResponse(await res.json());
102
+ if (res.status < 500 || !canRetry) {
103
+ throw new Error(`app icon request failed with ${res.status}`);
104
+ }
105
+ await delay(retryDelaysMs[attempt], signal);
106
+ }
107
+ }
108
+ function delay(ms, signal) {
109
+ return new Promise((resolve, reject)=>{
110
+ if (signal?.aborted) return reject(signal.reason);
111
+ const onAbort = ()=>{
112
+ clearTimeout(timer);
113
+ reject(signal.reason);
114
+ };
115
+ const timer = setTimeout(()=>{
116
+ signal?.removeEventListener('abort', onAbort);
117
+ resolve();
118
+ }, ms);
119
+ signal?.addEventListener('abort', onAbort, {
120
+ once: true
121
+ });
122
+ });
123
+ }
66
124
  // Details (icon included) are immutable per installed build, so cache them
67
125
  // process-wide keyed by udid:bundleId — switching between apps re-applies
68
126
  // instantly. Rejections are evicted so a transient exec-ws failure retries on
69
127
  // the next foreground change instead of pinning the miss forever.
70
128
  const detailsCache = new Map();
71
- export function getIosAppDetails(run, udid, bundleId) {
72
- const key = `${udid}:${bundleId}`;
129
+ export function getIosAppDetails(run, udid, bundleId, options = {}) {
130
+ const key = `${udid}:${bundleId}:${options.includeIcon !== false}`;
73
131
  const cached = detailsCache.get(key);
74
132
  if (cached) return cached;
75
- const pending = fetchIosAppDetails(run, udid, bundleId).catch((err)=>{
133
+ const pending = fetchIosAppDetails(run, udid, bundleId, options).catch((err)=>{
76
134
  detailsCache.delete(key);
77
135
  throw err;
78
136
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ios-app-details.ts"],"sourcesContent":["import { type HostActionResult, type RunHostAction } from './exec-ws';\nimport { type ForegroundApp } from './types';\n\n/**\n * Host-side app-bundle introspection for iOS simulators, ported from the\n * serve-sim preview client (`client/utils/app-icon.ts`) so the Hub shows the\n * same details serve-sim's own app-detection panel does. Every lookup is a\n * typed host action on the middleware exec channel (serve-sim #136) — the same\n * transport `setAppearance` and logs already use — never a shell command.\n */\n\nexport type { HostActionResult as ExecResult, RunHostAction } from './exec-ws';\n\n/** The {@link ForegroundApp} fields resolvable from the installed app bundle. */\nexport type IosAppDetails = Pick<\n ForegroundApp,\n 'label' | 'version' | 'build' | 'minOS' | 'executable' | 'appPath' | 'iconDataUrl'\n>;\n\n/**\n * Resolve display name, versions, and icon for an installed app:\n * `app.container` (simctl get_app_container) → `app.infoPlist` (plutil JSON) →\n * `app.iconPath` + `file.readBase64` for a loose icon PNG. Returns null when the\n * bundle can't be located (e.g. the process is not a plain app — SpringBoard\n * has no user-visible container on some runtimes). Icons compiled solely into\n * Assets.car yield no `iconDataUrl`; callers should fall back to a placeholder.\n */\nexport async function fetchIosAppDetails(\n run: RunHostAction,\n udid: string,\n bundleId: string,\n): Promise<IosAppDetails | null> {\n const ctn: HostActionResult = await run('app.container', { udid, bundleId });\n if (ctn.exitCode !== 0) return null;\n const appPath = ctn.stdout.trim();\n if (!appPath) return null;\n\n const plist = await run('app.infoPlist', { path: `${appPath}/Info.plist` });\n let info: any = {};\n if (plist.exitCode === 0) {\n try {\n info = JSON.parse(plist.stdout);\n } catch {}\n }\n\n // Icon name: CFBundleIcons → primary → last CFBundleIconFiles entry (the\n // largest variant), with the legacy flat keys as fallbacks.\n let iconName: string | undefined;\n const primary =\n info?.CFBundleIcons?.CFBundlePrimaryIcon ?? info?.['CFBundleIcons~ipad']?.CFBundlePrimaryIcon;\n const iconFiles: string[] | undefined = primary?.CFBundleIconFiles ?? info?.CFBundleIconFiles;\n if (iconFiles && iconFiles.length > 0) iconName = iconFiles[iconFiles.length - 1];\n else if (typeof info?.CFBundleIconFile === 'string') iconName = info.CFBundleIconFile;\n\n let iconDataUrl: string | undefined;\n if (iconName) {\n // Loose PNGs commonly sit next to Assets.car under a handful of names.\n const candidates = [\n `${iconName}@3x.png`,\n `${iconName}@2x.png`,\n `${iconName}.png`,\n `${iconName}60x60@3x.png`,\n `${iconName}60x60@2x.png`,\n ];\n const find = await run('app.iconPath', { appPath, candidates });\n const iconPath = find.stdout.trim();\n if (iconPath) {\n const b64 = await run('file.readBase64', { path: iconPath });\n if (b64.exitCode === 0) {\n iconDataUrl = `data:image/png;base64,${b64.stdout.replace(/\\s+/g, '')}`;\n }\n }\n }\n\n return {\n appPath,\n label: info.CFBundleDisplayName ?? info.CFBundleName,\n version: info.CFBundleShortVersionString,\n build: info.CFBundleVersion,\n minOS: info.MinimumOSVersion,\n executable: info.CFBundleExecutable,\n iconDataUrl,\n };\n}\n\n// Details (icon included) are immutable per installed build, so cache them\n// process-wide keyed by udid:bundleId — switching between apps re-applies\n// instantly. Rejections are evicted so a transient exec-ws failure retries on\n// the next foreground change instead of pinning the miss forever.\nconst detailsCache = new Map<string, Promise<IosAppDetails | null>>();\n\nexport function getIosAppDetails(\n run: RunHostAction,\n udid: string,\n bundleId: string,\n): Promise<IosAppDetails | null> {\n const key = `${udid}:${bundleId}`;\n const cached = detailsCache.get(key);\n if (cached) return cached;\n const pending = fetchIosAppDetails(run, udid, bundleId).catch((err) => {\n detailsCache.delete(key);\n throw err;\n });\n detailsCache.set(key, pending);\n return pending;\n}\n"],"names":["fetchIosAppDetails","run","udid","bundleId","ctn","exitCode","appPath","stdout","trim","plist","path","info","JSON","parse","iconName","primary","CFBundleIcons","CFBundlePrimaryIcon","iconFiles","CFBundleIconFiles","length","CFBundleIconFile","iconDataUrl","candidates","find","iconPath","b64","replace","label","CFBundleDisplayName","CFBundleName","version","CFBundleShortVersionString","build","CFBundleVersion","minOS","MinimumOSVersion","executable","CFBundleExecutable","detailsCache","Map","getIosAppDetails","key","cached","get","pending","catch","err","delete","set"],"mappings":"AAmBA;;;;;;;CAOC,GACD,OAAO,eAAeA,mBACpBC,GAAkB,EAClBC,IAAY,EACZC,QAAgB;IAEhB,MAAMC,MAAwB,MAAMH,IAAI,iBAAiB;QAAEC;QAAMC;IAAS;IAC1E,IAAIC,IAAIC,QAAQ,KAAK,GAAG,OAAO;IAC/B,MAAMC,UAAUF,IAAIG,MAAM,CAACC,IAAI;IAC/B,IAAI,CAACF,SAAS,OAAO;IAErB,MAAMG,QAAQ,MAAMR,IAAI,iBAAiB;QAAES,MAAM,GAAGJ,QAAQ,WAAW,CAAC;IAAC;IACzE,IAAIK,OAAY,CAAC;IACjB,IAAIF,MAAMJ,QAAQ,KAAK,GAAG;QACxB,IAAI;YACFM,OAAOC,KAAKC,KAAK,CAACJ,MAAMF,MAAM;QAChC,EAAE,OAAM,CAAC;IACX;IAEA,yEAAyE;IACzE,4DAA4D;IAC5D,IAAIO;IACJ,MAAMC,UACJJ,MAAMK,eAAeC,uBAAuBN,MAAM,CAAC,qBAAqB,EAAEM;IAC5E,MAAMC,YAAkCH,SAASI,qBAAqBR,MAAMQ;IAC5E,IAAID,aAAaA,UAAUE,MAAM,GAAG,GAAGN,WAAWI,SAAS,CAACA,UAAUE,MAAM,GAAG,EAAE;SAC5E,IAAI,OAAOT,MAAMU,qBAAqB,UAAUP,WAAWH,KAAKU,gBAAgB;IAErF,IAAIC;IACJ,IAAIR,UAAU;QACZ,uEAAuE;QACvE,MAAMS,aAAa;YACjB,GAAGT,SAAS,OAAO,CAAC;YACpB,GAAGA,SAAS,OAAO,CAAC;YACpB,GAAGA,SAAS,IAAI,CAAC;YACjB,GAAGA,SAAS,YAAY,CAAC;YACzB,GAAGA,SAAS,YAAY,CAAC;SAC1B;QACD,MAAMU,OAAO,MAAMvB,IAAI,gBAAgB;YAAEK;YAASiB;QAAW;QAC7D,MAAME,WAAWD,KAAKjB,MAAM,CAACC,IAAI;QACjC,IAAIiB,UAAU;YACZ,MAAMC,MAAM,MAAMzB,IAAI,mBAAmB;gBAAES,MAAMe;YAAS;YAC1D,IAAIC,IAAIrB,QAAQ,KAAK,GAAG;gBACtBiB,cAAc,CAAC,sBAAsB,EAAEI,IAAInB,MAAM,CAACoB,OAAO,CAAC,QAAQ,KAAK;YACzE;QACF;IACF;IAEA,OAAO;QACLrB;QACAsB,OAAOjB,KAAKkB,mBAAmB,IAAIlB,KAAKmB,YAAY;QACpDC,SAASpB,KAAKqB,0BAA0B;QACxCC,OAAOtB,KAAKuB,eAAe;QAC3BC,OAAOxB,KAAKyB,gBAAgB;QAC5BC,YAAY1B,KAAK2B,kBAAkB;QACnChB;IACF;AACF;AAEA,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,kEAAkE;AAClE,MAAMiB,eAAe,IAAIC;AAEzB,OAAO,SAASC,iBACdxC,GAAkB,EAClBC,IAAY,EACZC,QAAgB;IAEhB,MAAMuC,MAAM,GAAGxC,KAAK,CAAC,EAAEC,UAAU;IACjC,MAAMwC,SAASJ,aAAaK,GAAG,CAACF;IAChC,IAAIC,QAAQ,OAAOA;IACnB,MAAME,UAAU7C,mBAAmBC,KAAKC,MAAMC,UAAU2C,KAAK,CAAC,CAACC;QAC7DR,aAAaS,MAAM,CAACN;QACpB,MAAMK;IACR;IACAR,aAAaU,GAAG,CAACP,KAAKG;IACtB,OAAOA;AACT"}
1
+ {"version":3,"sources":["../src/ios-app-details.ts"],"sourcesContent":["import { parseAppIconResponse } from './android-app-icon';\nimport { type HostActionResult, type RunHostAction } from './exec-ws';\nimport { type ForegroundApp } from './types';\n\n/**\n * Host-side app-bundle introspection for iOS simulators, ported from the\n * serve-sim preview client (`client/utils/app-icon.ts`) so the Hub shows the\n * same details serve-sim's own app-detection panel does. Every lookup is a\n * typed host action on the middleware exec channel (serve-sim #136) — the same\n * transport `setAppearance` and logs already use — never a shell command.\n */\n\nexport type { HostActionResult as ExecResult, RunHostAction } from './exec-ws';\n\n/** The {@link ForegroundApp} fields resolvable from the installed app bundle. */\nexport type IosAppDetails = Pick<\n ForegroundApp,\n 'label' | 'version' | 'build' | 'minOS' | 'executable' | 'appPath' | 'iconDataUrl'\n>;\n\n/**\n * Resolve display name, versions, and icon for an installed app:\n * `app.container` (simctl get_app_container) → `app.infoPlist` (plutil JSON) →\n * `app.iconPath` + `file.readBase64` for a loose icon PNG. Returns null when the\n * bundle can't be located (e.g. the process is not a plain app — SpringBoard\n * has no user-visible container on some runtimes). Icons compiled solely into\n * Assets.car yield no `iconDataUrl`; callers should fall back to a placeholder.\n * With `includeIcon: false` the icon is left to {@link fetchIosAppIcon} and the\n * result carries no `iconDataUrl` key.\n */\nexport async function fetchIosAppDetails(\n run: RunHostAction,\n udid: string,\n bundleId: string,\n { includeIcon = true }: { includeIcon?: boolean } = {},\n): Promise<IosAppDetails | null> {\n const ctn: HostActionResult = await run('app.container', { udid, bundleId });\n if (ctn.exitCode !== 0) return null;\n const appPath = ctn.stdout.trim();\n if (!appPath) return null;\n\n const plist = await run('app.infoPlist', { path: `${appPath}/Info.plist` });\n let info: any = {};\n if (plist.exitCode === 0) {\n try {\n info = JSON.parse(plist.stdout);\n } catch {}\n }\n\n // Icon name: CFBundleIcons → primary → last CFBundleIconFiles entry (the\n // largest variant), with the legacy flat keys as fallbacks.\n let iconName: string | undefined;\n const primary =\n info?.CFBundleIcons?.CFBundlePrimaryIcon ?? info?.['CFBundleIcons~ipad']?.CFBundlePrimaryIcon;\n const iconFiles: string[] | undefined = primary?.CFBundleIconFiles ?? info?.CFBundleIconFiles;\n if (iconFiles && iconFiles.length > 0) iconName = iconFiles[iconFiles.length - 1];\n else if (typeof info?.CFBundleIconFile === 'string') iconName = info.CFBundleIconFile;\n\n let iconDataUrl: string | undefined;\n if (includeIcon && iconName) {\n // Loose PNGs commonly sit next to Assets.car under a handful of names.\n const candidates = [\n `${iconName}@3x.png`,\n `${iconName}@2x.png`,\n `${iconName}.png`,\n `${iconName}60x60@3x.png`,\n `${iconName}60x60@2x.png`,\n ];\n const find = await run('app.iconPath', { appPath, candidates });\n const iconPath = find.stdout.trim();\n if (iconPath) {\n const b64 = await run('file.readBase64', { path: iconPath });\n if (b64.exitCode === 0) {\n iconDataUrl = `data:image/png;base64,${b64.stdout.replace(/\\s+/g, '')}`;\n }\n }\n }\n\n return {\n appPath,\n label: info.CFBundleDisplayName ?? info.CFBundleName,\n version: info.CFBundleShortVersionString,\n build: info.CFBundleVersion,\n minOS: info.MinimumOSVersion,\n executable: info.CFBundleExecutable,\n ...(includeIcon ? { iconDataUrl } : {}),\n };\n}\n\ntype FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;\n\n/** Waits before each retry of the icon request. */\nconst ICON_RETRY_DELAYS_MS = [1_000, 2_000, 4_000];\n\n/**\n * The app's icon from serve-sim's `/api/apps/icon` route (`appIconEndpoint` in\n * `/api`): one plain GET, so it also reaches a tunneled server whose exec-ws\n * socket the page cannot open. Null when the app has no loose icon PNG.\n *\n * Not cached: the route reads the installed bundle on each request, so a\n * reinstalled build shows its new icon. A network error or a 5xx (the route\n * answers 503 while simctl fails) is retried a few times. A 4xx is not,\n * because the same request gets the same answer.\n */\nexport async function fetchIosAppIcon(\n appIconUrl: string,\n bundleId: string,\n {\n fetchImpl = fetch,\n signal,\n retryDelaysMs = ICON_RETRY_DELAYS_MS,\n }: { fetchImpl?: FetchImpl; signal?: AbortSignal; retryDelaysMs?: readonly number[] } = {},\n): Promise<string | null> {\n const url = new URL(appIconUrl);\n url.searchParams.set('bundleId', bundleId);\n for (let attempt = 0; ; attempt++) {\n const canRetry = attempt < retryDelaysMs.length;\n let res: Response;\n try {\n res = await fetchImpl(url.toString(), { cache: 'no-store', signal });\n } catch (err) {\n if (signal?.aborted || !canRetry) throw err;\n await delay(retryDelaysMs[attempt]!, signal);\n continue;\n }\n if (res.ok) return parseAppIconResponse(await res.json());\n if (res.status < 500 || !canRetry) {\n throw new Error(`app icon request failed with ${res.status}`);\n }\n await delay(retryDelaysMs[attempt]!, signal);\n }\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n// Details (icon included) are immutable per installed build, so cache them\n// process-wide keyed by udid:bundleId — switching between apps re-applies\n// instantly. Rejections are evicted so a transient exec-ws failure retries on\n// the next foreground change instead of pinning the miss forever.\nconst detailsCache = new Map<string, Promise<IosAppDetails | null>>();\n\nexport function getIosAppDetails(\n run: RunHostAction,\n udid: string,\n bundleId: string,\n options: { includeIcon?: boolean } = {},\n): Promise<IosAppDetails | null> {\n const key = `${udid}:${bundleId}:${options.includeIcon !== false}`;\n const cached = detailsCache.get(key);\n if (cached) return cached;\n const pending = fetchIosAppDetails(run, udid, bundleId, options).catch((err) => {\n detailsCache.delete(key);\n throw err;\n });\n detailsCache.set(key, pending);\n return pending;\n}\n"],"names":["parseAppIconResponse","fetchIosAppDetails","run","udid","bundleId","includeIcon","ctn","exitCode","appPath","stdout","trim","plist","path","info","JSON","parse","iconName","primary","CFBundleIcons","CFBundlePrimaryIcon","iconFiles","CFBundleIconFiles","length","CFBundleIconFile","iconDataUrl","candidates","find","iconPath","b64","replace","label","CFBundleDisplayName","CFBundleName","version","CFBundleShortVersionString","build","CFBundleVersion","minOS","MinimumOSVersion","executable","CFBundleExecutable","ICON_RETRY_DELAYS_MS","fetchIosAppIcon","appIconUrl","fetchImpl","fetch","signal","retryDelaysMs","url","URL","searchParams","set","attempt","canRetry","res","toString","cache","err","aborted","delay","ok","json","status","Error","ms","Promise","resolve","reject","reason","onAbort","clearTimeout","timer","setTimeout","removeEventListener","addEventListener","once","detailsCache","Map","getIosAppDetails","options","key","cached","get","pending","catch","delete"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useIosDevice.d.ts","sourceRoot":"","sources":["../src/useIosDevice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+BH,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,uBAAuB,EAmB7B,MAAM,SAAS,CAAC;AA2CjB,eAAO,MAAM,oBAAoB,MAAO,CAAC;AAgEzC;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7D;AA0DD,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CA8pCjF"}
1
+ {"version":3,"file":"useIosDevice.d.ts","sourceRoot":"","sources":["../src/useIosDevice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+BH,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,uBAAuB,EAmB7B,MAAM,SAAS,CAAC;AA2CjB,eAAO,MAAM,oBAAoB,MAAO,CAAC;AAgEzC;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7D;AA6DD,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CAqrCjF"}
@@ -32,7 +32,7 @@ import { HID_EDGE_BOTTOM, homeIndicatorEdge, rawDeltaForDisplayDelta, rawEdgeFor
32
32
  import { startIosHelper } from "./connections.mjs";
33
33
  import { clearIosEventLogState, createIosEventLogState, mergeIosEventLogPayload } from "./ios-events.mjs";
34
34
  import { hostUiRequest, runHostAction } from "./exec-ws.mjs";
35
- import { getIosAppDetails } from "./ios-app-details.mjs";
35
+ import { fetchIosAppIcon, getIosAppDetails } from "./ios-app-details.mjs";
36
36
  import { clearIosLocation, setIosLocation } from "./ios-location.mjs";
37
37
  import { fetchScreenshot } from "./screenshot.mjs";
38
38
  import { hidUsageForCode } from "./keyboard.mjs";
@@ -535,6 +535,7 @@ function connectionKey(baseUrl, device, token) {
535
535
  // server validates them against its internal middleware mount.
536
536
  logsPath: c.logsEndpoint ?? null,
537
537
  appStateUrl: appStateUrl && withSessionTokenQuery(appStateUrl, token),
538
+ appIconUrl: absoluteMiddlewareUrl(c.appIconEndpoint),
538
539
  eventsPath: c.eventLogEventsEndpoint ?? null,
539
540
  metricsPath: c.metricsEndpoint ?? null,
540
541
  axUrl: absoluteMiddlewareUrl(c.axEndpoint),
@@ -1192,12 +1193,17 @@ function connectionKey(baseUrl, device, token) {
1192
1193
  ]);
1193
1194
  // ── Foreground app details (name, versions, icon) — introspected from the
1194
1195
  // app bundle on the host over exec-ws whenever the foreground bundle id
1195
- // changes. Cached per udid:bundleId, so revisits apply instantly. ──
1196
+ // changes. Cached per udid:bundleId, so revisits apply instantly. A server
1197
+ // with the icon route serves the icon over plain HTTP instead, fetched
1198
+ // again on every foreground change. ──
1196
1199
  const foregroundAppId = foregroundApp?.id ?? null;
1200
+ const appIconUrl = config?.appIconUrl ?? null;
1197
1201
  useEffect(()=>{
1198
1202
  if (!foregroundAppId || !runAction || !deviceUdid) return;
1199
1203
  let cancelled = false;
1200
- getIosAppDetails(runAction, deviceUdid, foregroundAppId).then((details)=>{
1204
+ getIosAppDetails(runAction, deviceUdid, foregroundAppId, {
1205
+ includeIcon: !appIconUrl
1206
+ }).then((details)=>{
1201
1207
  if (cancelled || !details) return;
1202
1208
  setForegroundApp((prev)=>prev && prev.id === foregroundAppId ? {
1203
1209
  ...prev,
@@ -1211,7 +1217,28 @@ function connectionKey(baseUrl, device, token) {
1211
1217
  }, [
1212
1218
  foregroundAppId,
1213
1219
  runAction,
1214
- deviceUdid
1220
+ deviceUdid,
1221
+ appIconUrl
1222
+ ]);
1223
+ useEffect(()=>{
1224
+ if (!foregroundAppId || !appIconUrl) return;
1225
+ const controller = new AbortController();
1226
+ fetchIosAppIcon(appIconUrl, foregroundAppId, {
1227
+ fetchImpl: sessionFetch,
1228
+ signal: controller.signal
1229
+ }).then((iconDataUrl)=>{
1230
+ if (controller.signal.aborted || !iconDataUrl) return;
1231
+ setForegroundApp((prev)=>prev && prev.id === foregroundAppId ? {
1232
+ ...prev,
1233
+ iconDataUrl
1234
+ } : prev);
1235
+ }).catch(()=>{
1236
+ /* route unavailable or app not installed — the placeholder still renders */ });
1237
+ return ()=>controller.abort();
1238
+ }, [
1239
+ foregroundAppId,
1240
+ appIconUrl,
1241
+ sessionFetch
1215
1242
  ]);
1216
1243
  // ── Running simulators (middleware /grid/api) ──
1217
1244
  const gridApiUrl = config?.gridApiUrl ?? null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useIosDevice.ts"],"sourcesContent":["/**\n * serve-sim (iOS) implementation of the {@link DeviceClient} interface.\n *\n * Mirrors the serve-sim web client's architecture: the entry point is the\n * serve-sim **middleware** (default `:3200`), not the bare streaming helper.\n *\n * 1. `GET <base>/api` → the live config: the helper `url`/`streamUrl`/`wsUrl`,\n * the `device` udid, the per-session `execToken`, and the `logsEndpoint` /\n * `gridApiEndpoint` route paths.\n * 2. Video: MJPEG `<img>` from the helper's `streamUrl`. Input + screen config:\n * the helper's binary WebSocket (`0x03` touch, `0x04` button, `0x05`\n * multi-touch, `0x06` key, `0x0b` scroll, `0x0e` hardware keyboard out;\n * `0x82` screen config in). Coordinates are mapped to the device's raw\n * frame per orientation (see `./orientation`). Input sent while the socket\n * is reconnecting is queued briefly (see `./ws-send-queue`).\n * 3. Logs: streamed over the middleware's **exec-ws** WebSocket exactly like\n * the serve-sim client — `{token}` → `{sub, path: logsEndpoint}` → `{sub,\n * data}` (raw SSE) — rather than a direct route on the helper (the helper\n * has none). One-shot host actions (`{id, action, params}`) and\n * simulator-settings requests (`{id, ui}`) share that channel (`./exec-ws`).\n * 4. Devices: `GET <base>/grid/api`.\n *\n * `baseUrl` is always the mounted serve-sim middleware. Connection failures\n * keep retrying middleware discovery; they must not be reinterpreted as a bare\n * helper because doing so drops the middleware/helper path from stream URLs.\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';\n\nimport { AVCC_FRAME_TIMEOUT_MS, avccFallbackReducer, initialAvccFallback } from './avcc-fallback';\nimport {\n appendActivitySample,\n parseActivityHostCores,\n parseActivitySample,\n} from './activity';\nimport { type AccessibilityLoader, loadIosAccessibility } from './accessibility';\nimport { isAvccSupported } from './avcc';\nimport {\n HID_EDGE_BOTTOM,\n homeIndicatorEdge,\n rawDeltaForDisplayDelta,\n rawEdgeForDisplayEdge,\n rawPointForDisplayPoint,\n streamGeometry,\n} from './orientation';\nimport { startIosHelper } from './connections';\nimport {\n clearIosEventLogState,\n createIosEventLogState,\n mergeIosEventLogPayload,\n} from './ios-events';\nimport { hostUiRequest, runHostAction } from './exec-ws';\nimport { getIosAppDetails } from './ios-app-details';\nimport { clearIosLocation, setIosLocation } from './ios-location';\nimport { fetchScreenshot } from './screenshot';\nimport { hidUsageForCode } from './keyboard';\nimport {\n type ConnectionStatus,\n type DeviceActivity,\n type DeviceAppearance,\n type DeviceClient,\n type DeviceCapabilities,\n type DeviceConnectionOptions,\n type DeviceLog,\n type DeviceSettingKey,\n type DeviceSettings,\n type DeviceStreamCapabilities,\n type DeviceStreamEncoderSettings,\n type DeviceStreamSettingCapabilities,\n type DeviceWebRtcCodec,\n type DeviceOrientation,\n type ForegroundApp,\n type HardwareButton,\n type HidKeyEvent,\n type KeyboardInput,\n type MultiTouchSample,\n type RunningDevice,\n type ScreenSize,\n type ScreenshotCapture,\n type ScrollSample,\n type TouchSample,\n} from './types';\nimport { NO_PENDING_CAMERA_WRITES } from './device-camera';\nimport { mergeAuthoritativeDeviceSetting } from './device-setting-writes';\nimport { KeyedWriteTracker } from './keyed-write-tracker';\nimport { createPacedKeySender } from './paced-key-sender';\nimport { middlewareEndpointForBrowser, proxyPreviewConfigForBrowser } from './proxy-preview-config';\nimport { sessionTokenFetch, sessionTokenProtocols, withSessionTokenQuery } from './session-token';\nimport { type ParsedSseBlock, drainSseChunk } from './sse';\nimport { normalizeDeviceStreamSettings } from './stream-settings';\nimport { useAccessibility } from './useAccessibility';\nimport { useAppPermissions } from './useAppPermissions';\nimport { useAvccStream } from './useAvccStream';\nimport { type DeviceLocationBackend, useDeviceLocation } from './useDeviceLocation';\nimport { useStreamSettingsResource } from './useStreamSettingsResource';\nimport { useWebRtcStream, type WebRtcIceServer } from './useWebRtcStream';\nimport { presentedVideoFrameDelta } from './video-frame-metadata';\nimport {\n type WebRtcCodec,\n webRtcFallbackDecision,\n} from './webrtc-fallback';\nimport {\n flushWsMessageQueue,\n type QueuedWsMessage,\n sendOrQueueWsMessage,\n} from './ws-send-queue';\n\nconst MAX_LOGS = 200;\nconst RECONNECT_MS = 1500;\nconst ACTIVITY_STALE_MS = 8000;\nconst noop = () => {};\n\n// serve-sim binary WS message tags (serve-sim-client `SimulatorView`).\nconst WS_MSG_TOUCH = 0x03;\nconst WS_MSG_BUTTON = 0x04;\nconst WS_MSG_MULTI_TOUCH = 0x05;\nconst WS_MSG_KEY = 0x06;\nconst WS_MSG_ORIENTATION = 0x07;\n// Native scroll (wheel/trackpad) in raw device fractions, anchored under the pointer.\nconst WS_MSG_SCROLL = 0x0b;\nconst WS_MSG_SOFTWARE_KEYBOARD = 0x0c;\n// Connect/disconnect the guest's hardware keyboard; serve-sim's own touch\n// client sends this too so the on-screen keyboard shows.\nconst WS_MSG_HARDWARE_KEYBOARD = 0x0e;\nexport const WS_TAG_SCREEN_CONFIG = 0x82;\n\n// HID keyboard usage codes (USB HID Usage Page 0x07) for the R reload chord.\nconst HID_USAGE_R = 0x15; // 'r'\n\n// The simulator-settings option behind `hardwareKeyboardConnected`.\nconst UI_OPTION_HARDWARE_KEYBOARD = 'hardware-keyboard';\n\nconst PLACEHOLDER_DEVICES: RunningDevice[] = [\n { id: 'ios', name: 'iPhone Simulator', platform: 'ios', current: true },\n];\n\nconst IOS_STREAM_CAPABILITIES = {\n modeAvailability: { mjpeg: true, h264: true, webrtc: true },\n httpCodecs: ['auto', 'h264', 'mjpeg'],\n webRtcCodecs: ['h264', 'vp9', 'vp8'],\n} as const satisfies DeviceStreamCapabilities;\n\nconst IOS_STREAM_SETTING_CAPABILITIES = {\n mjpegFps: true,\n mjpegQuality: true,\n maxDimension: true,\n h264Bitrate: true,\n h264Fps: true,\n} as const satisfies DeviceStreamSettingCapabilities;\n\n// The counterclockwise rotation order (matches Simulator's \"Rotate Left\"): each\n// press advances one step, so four presses come back around to portrait.\nconst ORIENTATION_CYCLE: DeviceOrientation[] = [\n 'portrait',\n 'landscape_left',\n 'portrait_upside_down',\n 'landscape_right',\n];\n\n// iOS only has a Home button + app switcher; the rest are no-ops.\nconst BUTTON_NAME: Record<HardwareButton, string | null> = {\n home: 'home',\n appSwitcher: 'app_switcher',\n power: 'lock',\n back: null,\n recents: null,\n hideKeyboard: null,\n};\n\nconst decoder = new TextDecoder();\n\nfunction parseIosStreamSettings(\n value: unknown,\n fallback: DeviceStreamEncoderSettings,\n): DeviceStreamEncoderSettings {\n return normalizeDeviceStreamSettings(value, fallback);\n}\n\nfunction iosStreamSettingsPatch(\n patch: Partial<DeviceStreamEncoderSettings>,\n): Partial<DeviceStreamEncoderSettings> | null {\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction toWs(url: string): string {\n return url.replace(/^http/, 'ws');\n}\n\n/**\n * `…/helper/<udid>/ws` -> `…/helper/ws?device=<udid>`\n * serve-sim\n */\nexport function toQueryStyleHelperWsUrl(wsUrl: string): string {\n const url = new URL(wsUrl);\n const match = url.pathname.match(/^(.*\\/helper)\\/([^/]+)\\/ws$/);\n if (!match) throw new Error(`Invalid helper ws url, no deviceId matched: ${wsUrl}`);\n url.pathname = `${match[1]}/ws`;\n if (!url.searchParams.has('device')) {\n url.searchParams.set('device', decodeURIComponent(match[2]));\n }\n return url.toString();\n}\n\n/** Resolved connection: where to stream video/input, and how to reach logs/devices. */\ninterface ResolvedConfig {\n /** Base serve-sim helper URL used by `/stream.avcc`. */\n url: string;\n streamUrl: string;\n wsUrl: string;\n device: string | null;\n /** Middleware exec-ws URL used for logs, events, metrics, and UI requests. */\n execWsUrl: string | null;\n execToken: string | null;\n /** Relative SSE path to subscribe for logs, e.g. `/logs?device=<udid>`. */\n logsPath: string | null;\n /** Absolute URL of the foreground-app SSE stream. */\n appStateUrl: string | null;\n /** Relative SSE path for normalized serve-sim events. */\n eventsPath: string | null;\n /** Relative SSE path for foreground app activity. */\n metricsPath: string | null;\n axUrl: string | null;\n /** Runtime encoder settings endpoint on the selected helper. */\n streamSettingsUrl: string | null;\n /** Initial server-provided stream settings, if present. */\n initialStreamSettings: unknown;\n gridApiUrl: string | null;\n webRtcCodec: WebRtcCodec;\n webRtcIceServers?: WebRtcIceServer[];\n}\n\n/** Shape of the serve-sim middleware `/api` (and grid) responses we read. */\ninterface PreviewApi {\n url?: string;\n streamUrl?: string;\n wsUrl?: string;\n device?: string;\n basePath?: string;\n execToken?: string;\n logsEndpoint?: string;\n appStateEndpoint?: string;\n eventLogEventsEndpoint?: string;\n metricsEndpoint?: string;\n axEndpoint?: string;\n streamSettingsEndpoint?: string;\n gridApiEndpoint?: string;\n proxyHelpers?: boolean;\n streamSettings?:\n | ({ transport: 'http'; codec?: 'auto' | 'h264' | 'mjpeg' } &\n Partial<DeviceStreamEncoderSettings>)\n | ({ transport: 'webrtc'; codec: WebRtcCodec; iceServers?: WebRtcIceServer[] } &\n Partial<DeviceStreamEncoderSettings>);\n}\n\n// The server, device, and token that one resolved config belongs to.\nfunction connectionKey(baseUrl: string, device: string | null, token: string | null): string {\n return JSON.stringify([baseUrl, device, token]);\n}\n\n/** @deprecated Use DeviceClientProvider with useDeviceClient or useDeviceScreenClient instead. */\nexport function useIosDeviceClient(options: DeviceConnectionOptions): DeviceClient {\n const { baseUrl, enabled = true, device: targetDevice = null, streamMode, token = null } = options;\n const active = enabled && !!baseUrl;\n const sessionFetch = useMemo(() => sessionTokenFetch(token), [token]);\n const socketProtocols = useMemo(() => sessionTokenProtocols('ios', token), [token]);\n\n const [status, setStatus] = useState<ConnectionStatus>('idle');\n const [error, setError] = useState<string | null>(null);\n const [screen, setScreen] = useState<ScreenSize | null>(null);\n const [fps, setFps] = useState(0);\n const [logs, setLogs] = useState<DeviceLog[]>([]);\n // Logs are opt-in: nothing streams until the user attaches.\n const [logsEnabled, setLogsEnabled] = useState(false);\n const [eventLogState, setEventLogState] = useState(createIosEventLogState);\n const events = eventLogState.events;\n const [eventsEnabled, setEventsEnabled] = useState(false);\n const [activity, setActivity] = useState<DeviceActivity | null>(null);\n const [devices, setDevices] = useState<RunningDevice[]>(PLACEHOLDER_DEVICES);\n // The credentials above follow the options at once, but a new config waits for `/api`. Until\n // it arrives, the old config is not used, so its URLs never get another connection's token.\n // Effect cleanups still close the old connections with the credentials they opened them with.\n const [resolvedConfig, setResolvedConfig] = useState<{\n key: string;\n config: ResolvedConfig;\n } | null>(null);\n const config =\n active && baseUrl && resolvedConfig?.key === connectionKey(baseUrl, targetDevice, token)\n ? resolvedConfig.config\n : null;\n // The simulator's system dark/light setting. null until read.\n const [appearance, setAppearanceState] = useState<DeviceAppearance | null>(null);\n const [deviceSettings, setDeviceSettings] = useState<DeviceSettings | null>(null);\n const [deviceSettingsPending, setDeviceSettingsPending] = useState<\n ReadonlySet<DeviceSettingKey>\n >(() => new Set());\n // Browser HID injection remains active when this is false. Disabling the\n // Simulator-owned host connection lets iOS keep its software keyboard open.\n const [hardwareKeyboardConnected, setHardwareKeyboardConnectedState] = useState<boolean | null>(\n null,\n );\n // The frontmost app, pushed by the middleware's /appstate SSE. null until the\n // first event.\n const [foregroundApp, setForegroundApp] = useState<ForegroundApp | null>(null);\n\n const wsRef = useRef<WebSocket | null>(null);\n // Input that arrived while the helper socket was down; flushed on reconnect\n // (bounded, and stale entries are dropped — see `./ws-send-queue`).\n const pendingWsRef = useRef<QueuedWsMessage[]>([]);\n // Monotonic log id source, persisted across log-stream reconnects so ids stay\n // unique even though lines are kept (the stream effect may re-run).\n const logSeqRef = useRef(0);\n const imgRef = useRef<HTMLImageElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const streamUrlRef = useRef<string | null>(null);\n const [avccFallback, dispatchAvccFallback] = useReducer(avccFallbackReducer, initialAvccFallback);\n const [webRtcCodec, setWebRtcCodecState] = useState<DeviceWebRtcCodec>('h264');\n const [activeWebRtcCodec, setActiveWebRtcCodec] = useState<WebRtcCodec>('h264');\n const [webRtcHttpFallback, setWebRtcHttpFallback] = useState(false);\n const deviceSettingWriteTrackerRef = useRef(new KeyedWriteTracker<DeviceSettingKey>());\n // Async option writes capture their config. Track only committed config so\n // an interrupted concurrent render cannot invalidate a legitimate rollback.\n const deviceSettingConfigRef = useRef(config);\n useLayoutEffect(() => {\n deviceSettingConfigRef.current = config;\n }, [config]);\n const activityLastSampleAtRef = useRef(0);\n const useWebRtc = streamMode === 'webrtc' && !webRtcHttpFallback;\n const wantsAvcc = streamMode === 'h264' || webRtcHttpFallback;\n const useAvcc = wantsAvcc && isAvccSupported() && !avccFallback.fellBack;\n useEffect(() => {\n if (streamMode === 'webrtc') return;\n setWebRtcHttpFallback(false);\n setActiveWebRtcCodec(webRtcCodec);\n }, [streamMode, webRtcCodec]);\n // True while the in-flight single-finger drag began in the home-indicator band.\n const edgeGestureRef = useRef(false);\n // Latest screen config, read by the (stable) input callbacks for orientation.\n const screenRef = useRef<ScreenSize | null>(null);\n useEffect(() => {\n screenRef.current = screen;\n }, [screen]);\n // Once the helper WS pushes a config, it owns dimensions+orientation.\n const hasWsConfigRef = useRef(false);\n\n const applyStreamSrc = useCallback(() => {\n const img = imgRef.current;\n const url = streamUrlRef.current;\n if (!img || !url) return;\n img.src = `${url}${url.includes('?') ? '&' : '?'}t=${Date.now()}`;\n }, []);\n\n const attachVideo = useCallback(\n (el: HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | null) => {\n if (useWebRtc) {\n videoRef.current = (el as HTMLVideoElement) ?? null;\n canvasRef.current = null;\n imgRef.current = null;\n } else if (useAvcc) {\n canvasRef.current = (el as HTMLCanvasElement) ?? null;\n videoRef.current = null;\n imgRef.current = null;\n } else {\n imgRef.current = (el as HTMLImageElement) ?? null;\n canvasRef.current = null;\n videoRef.current = null;\n if (el) applyStreamSrc();\n }\n },\n [applyStreamSrc, useAvcc, useWebRtc],\n );\n\n // Every helper-socket message goes through here so a brief reconnect queues\n // input instead of dropping it (matching serve-sim's client).\n const sendWs = useCallback((tag: number, payload: object) => {\n pendingWsRef.current = sendOrQueueWsMessage(wsRef.current, pendingWsRef.current, tag, payload);\n }, []);\n\n const sendTouch = useCallback((sample: TouchSample) => {\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n\n let displayEdge: number | undefined;\n if (sample.phase === 'begin') {\n edgeGestureRef.current = homeIndicatorEdge(sample) !== undefined;\n if (edgeGestureRef.current) displayEdge = HID_EDGE_BOTTOM;\n } else if (edgeGestureRef.current) {\n displayEdge = HID_EDGE_BOTTOM;\n if (sample.phase === 'end') edgeGestureRef.current = false;\n }\n\n const p = rawPointForDisplayPoint(orientation, sample.x, sample.y);\n const edge = displayEdge === undefined ? undefined : rawEdgeForDisplayEdge(orientation, displayEdge);\n const payload =\n edge === undefined ? { type: sample.phase, ...p } : { type: sample.phase, ...p, edge };\n sendWs(WS_MSG_TOUCH, payload);\n }, [sendWs]);\n\n const sendMultiTouch = useCallback(\n (sample: MultiTouchSample) => {\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n const a = rawPointForDisplayPoint(orientation, sample.a.x, sample.a.y);\n const b = rawPointForDisplayPoint(orientation, sample.b.x, sample.b.y);\n sendWs(WS_MSG_MULTI_TOUCH, { type: sample.phase, x1: a.x, y1: a.y, x2: b.x, y2: b.y });\n },\n [sendWs],\n );\n\n // Scroll-to-pan: forwarded as a native scroll event so iOS pans content\n // exactly as it would for a physical wheel — no synthesized finger drag.\n // Both the delta and the cursor anchor are rotated into raw device\n // orientation so scrolling tracks the visible content on rotated devices.\n const sendScroll = useCallback(\n (sample: ScrollSample) => {\n if (!Number.isFinite(sample.dx) || !Number.isFinite(sample.dy)) return;\n if (sample.dx === 0 && sample.dy === 0) return;\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n const delta = rawDeltaForDisplayDelta(orientation, sample.dx, sample.dy);\n const anchor = rawPointForDisplayPoint(orientation, sample.x, sample.y);\n sendWs(WS_MSG_SCROLL, { dx: delta.dx, dy: delta.dy, x: anchor.x, y: anchor.y });\n },\n [sendWs],\n );\n\n const sendKey = useCallback(\n (input: KeyboardInput): boolean => {\n const usage = hidUsageForCode(input.code);\n if (usage === null) return false;\n sendWs(WS_MSG_KEY, { type: input.phase, usage });\n return true;\n },\n [sendWs],\n );\n\n // Pre-mapped key events (phone-keyboard capture) are paced a few ms apart so\n // iOS doesn't coalesce a pasted string into a couple of lost keystrokes.\n const keySender = useMemo(\n () =>\n createPacedKeySender((event) => sendWs(WS_MSG_KEY, { type: event.type, usage: event.usage })),\n [sendWs],\n );\n useEffect(() => () => keySender.dispose(), [keySender]);\n const sendKeyEvents = useCallback(\n (events: ReadonlyArray<HidKeyEvent>) => keySender.enqueue(events),\n [keySender],\n );\n\n // Connect/disconnect the Mac keyboard from the guest through serve-sim's\n // `hardware-keyboard` simulator setting (the same request its settings panel\n // makes). Optimistic; reverted if the middleware rejects it.\n const setHardwareKeyboardConnected = useCallback(\n (connected: boolean) => {\n const c = config;\n if (!c || !c.execWsUrl || !c.execToken || !c.device) return;\n const previous = hardwareKeyboardConnected;\n setHardwareKeyboardConnectedState(connected);\n void hostUiRequest(\n c.execWsUrl,\n c.execToken,\n {\n device: c.device,\n option: UI_OPTION_HARDWARE_KEYBOARD,\n value: connected ? 'on' : 'off',\n },\n socketProtocols,\n ).catch(() => setHardwareKeyboardConnectedState(previous));\n },\n [config, hardwareKeyboardConnected, socketProtocols],\n );\n\n const toggleSoftwareKeyboard = useCallback(() => {\n sendWs(WS_MSG_SOFTWARE_KEYBOARD, {});\n }, [sendWs]);\n\n const pressButton = useCallback(\n (button: HardwareButton) => {\n const name = BUTTON_NAME[button];\n if (name) sendWs(WS_MSG_BUTTON, { button: name });\n },\n [sendWs],\n );\n\n // Reload the RN/Expo bundle by injecting ⌘R over the helper's key channel\n // (tag 0x06 → HID keystroke) — RN registers ⌘R as its reload shortcut. Mirrors\n // the serve-sim web client's sequence exactly: ⌘ down, R down, R up, ⌘ up, with\n // a sequential 30ms await between each event (so the gaps can't compress under\n // timer jitter). Harmless if the foreground app isn't RN.\n const reload = useCallback(async () => {\n const key = (type: 'down' | 'up', usage: number) => sendWs(WS_MSG_KEY, { type, usage });\n key('down', HID_USAGE_R);\n await new Promise((r) => setTimeout(r, 30));\n key('up', HID_USAGE_R);\n }, [sendWs]);\n\n // Rotate one step counterclockwise from the last known orientation, over the\n // helper's orientation channel (tag 0x07 → HID orientation event). The helper\n // confirms by pushing an updated screen config, which keeps the cycle in sync.\n const rotate = useCallback(() => {\n const current = screenRef.current?.orientation ?? 'portrait';\n const next =\n ORIENTATION_CYCLE[(ORIENTATION_CYCLE.indexOf(current) + 1) % ORIENTATION_CYCLE.length];\n sendWs(WS_MSG_ORIENTATION, { orientation: next });\n }, [sendWs]);\n\n // serve-sim's middleware captures the sim via `simctl io <udid> screenshot`\n // and returns the PNG bytes. Use the resolved udid from `/api` (falling back\n // to the requested device); the middleware defaults to the booted sim if none.\n const screenshot = useCallback(async (): Promise<ScreenshotCapture | null> => {\n if (!baseUrl) return null;\n const udid = config?.device ?? targetDevice;\n return fetchScreenshot(baseUrl, udid, sessionFetch);\n }, [baseUrl, targetDevice, config, sessionFetch]);\n\n // Apply any serve-sim UI option over its authenticated exec-ws request\n // channel. The state is optimistic so the selected pill/switch responds at\n // once. Writes are serialized per option, while unrelated options can update\n // concurrently. A failed request re-reads only that option's authoritative\n // value so it cannot roll back another optimistic write.\n const setDeviceSetting = useCallback(\n (key: DeviceSettingKey, value: string) => {\n const c = config;\n if (!c?.execWsUrl || !c.execToken || !c.device) return;\n const { device, execToken, execWsUrl } = c;\n const tracker = deviceSettingWriteTrackerRef.current;\n const request = tracker.start(key);\n if (!request) return;\n setDeviceSettingsPending(tracker.pending);\n setDeviceSettings((current) => ({ ...(current ?? {}), [key]: value }));\n if (key === 'appearance' && (value === 'light' || value === 'dark')) {\n setAppearanceState(value);\n }\n void hostUiRequest(execWsUrl, execToken, { device, option: key, value }, socketProtocols)\n .catch(async () => {\n if (!tracker.isCurrent(request) || deviceSettingConfigRef.current !== c) return;\n try {\n const result = await hostUiRequest(execWsUrl, execToken, { device }, socketProtocols);\n if (!tracker.isCurrent(request) || deviceSettingConfigRef.current !== c) return;\n const authoritative: DeviceSettings = {};\n for (const [nextKey, nextValue] of Object.entries(result.status ?? {})) {\n if (typeof nextValue === 'string') {\n authoritative[nextKey as DeviceSettingKey] = nextValue;\n }\n }\n setDeviceSettings((current) =>\n mergeAuthoritativeDeviceSetting(current, key, authoritative),\n );\n if (key === 'appearance') {\n const nextAppearance = authoritative.appearance;\n if (nextAppearance === 'light' || nextAppearance === 'dark') {\n setAppearanceState(nextAppearance);\n }\n }\n } catch {\n // Keep the optimistic value if both the write and authoritative\n // refresh channels are temporarily unavailable.\n }\n })\n .finally(() => {\n if (tracker.finish(request)) setDeviceSettingsPending(tracker.pending);\n });\n },\n [config, socketProtocols],\n );\n\n const setAppearance = useCallback(\n (mode: DeviceAppearance) => setDeviceSetting('appearance', mode),\n [setDeviceSetting],\n );\n\n const setWebRtcCodec = useCallback((codec: DeviceWebRtcCodec) => {\n setWebRtcCodecState(codec);\n setActiveWebRtcCodec(codec);\n setWebRtcHttpFallback(false);\n }, []);\n\n const attachLogs = useCallback(() => setLogsEnabled(true), []);\n const detachLogs = useCallback(() => setLogsEnabled(false), []);\n const clearLogs = useCallback(() => setLogs([]), []);\n const attachEvents = useCallback(() => setEventsEnabled(true), []);\n const detachEvents = useCallback(() => setEventsEnabled(false), []);\n const clearEvents = useCallback(() => {\n const device = config?.device;\n if (!device) return;\n setEventLogState((current) => clearIosEventLogState(current, device));\n }, [config?.device]);\n\n // ── Resolve the connection: discover the helper + log/device routes via /api. ──\n //\n // The Hub starts helpers explicitly (see `startIosHelper`) — it never boots a\n // sim just by connecting. So when the middleware is reachable but no helper is\n // attached yet (`/api` → null), we keep polling until the just-started helper\n // comes up, then resolve its streaming config. An unreachable middleware is\n // retried: `baseUrl` is never interpreted as a bare helper.\n useEffect(() => {\n if (!active || !baseUrl) {\n setResolvedConfig(null);\n setStatus('idle');\n return;\n }\n let cancelled = false;\n let pollTimer: ReturnType<typeof setTimeout> | null = null;\n setStatus('connecting');\n setError(null);\n\n // `baseUrl` may carry a path prefix (the plugin mount), so join onto it\n // rather than `new URL('/api', baseUrl)`, which would drop that prefix.\n const apiUrl = `${baseUrl.replace(/\\/$/, '')}/api${\n targetDevice ? `?device=${encodeURIComponent(targetDevice)}` : ''\n }`;\n\n const toMiddleware = (rawConfig: PreviewApi): ResolvedConfig => {\n const middlewareUrl = new URL(baseUrl, window.location.href);\n const c = proxyPreviewConfigForBrowser(rawConfig, middlewareUrl);\n const basePath = c.basePath === '/' ? '' : (c.basePath ?? '');\n const absoluteMiddlewareUrl = (path?: string): string | null =>\n path\n ? c.proxyHelpers\n ? middlewareEndpointForBrowser(path, middlewareUrl, basePath)\n : new URL(path, middlewareUrl).toString()\n : null;\n const appStateUrl = absoluteMiddlewareUrl(c.appStateEndpoint);\n return {\n url: c.url!,\n // An <img> and an EventSource cannot set a header.\n streamUrl: withSessionTokenQuery(c.streamUrl ?? `${c.url}/stream.mjpeg`, token),\n wsUrl: toQueryStyleHelperWsUrl(c.wsUrl ?? `${toWs(c.url!)}/ws`),\n device: c.device ?? null,\n execWsUrl: toWs(absoluteMiddlewareUrl(`${basePath}/exec-ws`)!),\n execToken: c.execToken ?? null,\n // These are subscription paths inside exec-ws, not browser URLs. The\n // server validates them against its internal middleware mount.\n logsPath: c.logsEndpoint ?? null,\n appStateUrl: appStateUrl && withSessionTokenQuery(appStateUrl, token),\n eventsPath: c.eventLogEventsEndpoint ?? null,\n metricsPath: c.metricsEndpoint ?? null,\n axUrl: absoluteMiddlewareUrl(c.axEndpoint),\n // A proxied helper URL uses the public middleware mount above rather\n // than an advertised internal host port that may be 0.\n streamSettingsUrl: c.streamSettingsEndpoint\n ? c.proxyHelpers\n ? `${c.url}/stream-settings`\n : absoluteMiddlewareUrl(c.streamSettingsEndpoint)\n : null,\n initialStreamSettings: c.streamSettings,\n gridApiUrl: absoluteMiddlewareUrl(c.gridApiEndpoint ?? `${basePath}/grid/api`),\n webRtcCodec: c.streamSettings?.transport === 'webrtc' ? c.streamSettings.codec : 'h264',\n ...(c.streamSettings?.transport === 'webrtc' && c.streamSettings.iceServers\n ? { webRtcIceServers: c.streamSettings.iceServers }\n : {}),\n };\n };\n\n // Ask the grid to attach a helper for this device at most once per effect\n // run (i.e. per device). Resets whenever `targetDevice`/`baseUrl` change.\n let startRequested = false;\n\n const resolve = async () => {\n if (cancelled) return;\n try {\n const res = await sessionFetch(apiUrl, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) {\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n return;\n }\n const c = (await res.json()) as PreviewApi | null;\n if (c && c.url && c.device) {\n if (!cancelled) {\n const resolved = toMiddleware(c);\n setWebRtcCodec(resolved.webRtcCodec);\n setResolvedConfig({\n key: connectionKey(baseUrl, targetDevice, token),\n config: resolved,\n });\n }\n return;\n }\n // Middleware reachable but no helper for this device yet. Ask the grid to\n // start one (once): a booted sim just gets a stream daemon; a shut-down\n // sim is booted. The middleware never does this on its own — only here,\n // because the user selected this device. Then poll until it attaches.\n if (targetDevice && !startRequested) {\n startRequested = true;\n void startIosHelper(targetDevice, baseUrl, sessionFetch).catch(() => {});\n }\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n } catch {\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n }\n };\n void resolve();\n\n return () => {\n cancelled = true;\n if (pollTimer) clearTimeout(pollTimer);\n };\n }, [active, baseUrl, targetDevice, setWebRtcCodec, sessionFetch, token]);\n\n const fpsCounterRef = useRef({ frames: 0, startedAt: 0 });\n const onAvccFrame = useCallback((frameDelta = 1) => {\n const now = performance.now();\n const counter = fpsCounterRef.current;\n if (counter.startedAt === 0) counter.startedAt = now;\n counter.frames += frameDelta;\n if (now - counter.startedAt < 1_000) return;\n const next = Math.round((counter.frames * 1_000) / (now - counter.startedAt));\n counter.frames = 0;\n counter.startedAt = now;\n setFps((previous) => (previous === next ? previous : next));\n }, []);\n\n // ── WebRTC with serve-sim's codec and HTTP fallback policy. ──\n const {\n stream: webRtcStream,\n failure: webRtcFailure,\n error: webRtcError,\n markFrameDecoded: markWebRtcFrameDecoded,\n streamStats,\n setStreamStatsEnabled,\n } = useWebRtcStream({\n offerUrl: config ? `${config.url}/webrtc/offer` : '',\n closeUrl: config ? `${config.url}/webrtc/close` : '',\n closeBeaconUrl: config ? withSessionTokenQuery(`${config.url}/webrtc/close`, token) : '',\n statsUrl: config ? `${config.url}/webrtc/stats` : '',\n enabled: active && useWebRtc && !!config,\n codec: activeWebRtcCodec,\n iceServers: config?.webRtcIceServers,\n fetchImpl: sessionFetch,\n });\n const handledWebRtcFailureRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!useWebRtc || !webRtcFailure) return;\n if (handledWebRtcFailureRef.current === webRtcFailure.sessionId) return;\n handledWebRtcFailureRef.current = webRtcFailure.sessionId;\n const decision = webRtcFallbackDecision(webRtcCodec, activeWebRtcCodec, webRtcFailure);\n if (!decision) return;\n if (decision.type === 'switch-to-http') setWebRtcHttpFallback(true);\n else setActiveWebRtcCodec(decision.codec);\n }, [useWebRtc, webRtcFailure, webRtcCodec, activeWebRtcCodec]);\n\n useEffect(() => {\n if (!useWebRtc) return;\n if (webRtcError) {\n setStatus('error');\n setError(webRtcError);\n } else if (!webRtcStream) {\n setStatus('connecting');\n setError(null);\n }\n }, [useWebRtc, webRtcError, webRtcStream]);\n\n useEffect(() => {\n if (!useWebRtc) return;\n const video = videoRef.current;\n if (!video) return;\n let stopped = false;\n let firstFrame = true;\n let frameCallback = 0;\n let previousPresentedFrames: number | null = null;\n\n const markFrame = (presentedFrameDelta = 1) => {\n if (stopped) return;\n const { videoWidth: width, videoHeight: height } = video;\n if (width > 0 && height > 0 && !hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === width &&\n prev.height === height\n ? prev\n : { width, height },\n );\n }\n onAvccFrame(presentedFrameDelta);\n markWebRtcFrameDecoded(presentedFrameDelta);\n if (firstFrame) {\n firstFrame = false;\n setStatus('streaming');\n setError(null);\n }\n };\n const onVideoFrame: VideoFrameRequestCallback = (_now, metadata) => {\n const presentedFrameDelta = presentedVideoFrameDelta(\n previousPresentedFrames,\n metadata.presentedFrames,\n );\n if (Number.isSafeInteger(metadata.presentedFrames) && metadata.presentedFrames >= 0) {\n previousPresentedFrames = metadata.presentedFrames;\n }\n markFrame(presentedFrameDelta);\n frameCallback = video.requestVideoFrameCallback(onVideoFrame);\n };\n const onTimeUpdate = () => markFrame();\n const onLoadedData = () => markFrame(0);\n\n video.srcObject = webRtcStream;\n if (webRtcStream) {\n const supportsVideoFrameCallback = typeof video.requestVideoFrameCallback === 'function';\n if (supportsVideoFrameCallback) frameCallback = video.requestVideoFrameCallback(onVideoFrame);\n else video.addEventListener('timeupdate', onTimeUpdate);\n video.addEventListener('loadeddata', onLoadedData, { once: true });\n void video.play().catch(() => {});\n }\n\n return () => {\n stopped = true;\n video.removeEventListener('loadeddata', onLoadedData);\n video.removeEventListener('timeupdate', onTimeUpdate);\n if (frameCallback && typeof video.cancelVideoFrameCallback === 'function') {\n video.cancelVideoFrameCallback(frameCallback);\n }\n video.srcObject = null;\n };\n }, [useWebRtc, webRtcStream, markWebRtcFrameDecoded, onAvccFrame]);\n\n // ── H.264 AVCC (WebCodecs) with serve-sim's MJPEG fallback policy. ──\n useEffect(() => {\n dispatchAvccFallback('reset');\n setWebRtcHttpFallback(false);\n setFps(0);\n }, [streamMode, config?.url, config?.webRtcCodec]);\n\n useEffect(() => {\n if (!useAvcc || !config?.url) return;\n const timer = setTimeout(() => dispatchAvccFallback('timeout'), AVCC_FRAME_TIMEOUT_MS);\n return () => clearTimeout(timer);\n }, [useAvcc, config?.url]);\n\n useAvccStream({\n url: config?.url ?? '',\n enabled: active && useAvcc && !!config,\n canvasRef,\n fetchImpl: sessionFetch,\n onFirstFrame: () => {\n setStatus('streaming');\n setError(null);\n },\n onFrame: onAvccFrame,\n onDecodedFrame: () => dispatchAvccFallback('decoded-frame'),\n onResize: (width, height) => {\n if (!hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === width &&\n prev.height === height\n ? prev\n : { width, height },\n );\n }\n },\n onError: (message) => {\n setStatus('error');\n setError(message);\n },\n onDecoderError: () => dispatchAvccFallback('error'),\n });\n\n // ── MJPEG video (<img>) ──\n const streamUrl = useAvcc || useWebRtc ? null : (config?.streamUrl ?? null);\n useEffect(() => {\n if (!streamUrl) {\n streamUrlRef.current = null;\n return;\n }\n streamUrlRef.current = streamUrl;\n setStatus('connecting');\n setError(null);\n\n let cancelled = false;\n let settled = false;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n const img = imgRef.current;\n\n const markStreaming = () => {\n if (cancelled || settled) return;\n const el = imgRef.current;\n if (!el || el.naturalWidth === 0 || el.naturalHeight === 0) return;\n settled = true;\n setStatus('streaming');\n setError(null);\n if (!hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === el.naturalWidth &&\n prev.height === el.naturalHeight\n ? prev\n : { width: el.naturalWidth, height: el.naturalHeight },\n );\n }\n };\n const onError = () => {\n if (cancelled) return;\n settled = false;\n setStatus('error');\n setError('Stream unavailable — retrying…');\n retryTimer = setTimeout(() => {\n if (!cancelled) applyStreamSrc();\n }, RECONNECT_MS);\n };\n img?.addEventListener('load', markStreaming);\n img?.addEventListener('error', onError);\n applyStreamSrc();\n const poll = setInterval(markStreaming, 400);\n\n return () => {\n cancelled = true;\n clearInterval(poll);\n if (retryTimer) clearTimeout(retryTimer);\n img?.removeEventListener('load', markStreaming);\n img?.removeEventListener('error', onError);\n const el = imgRef.current;\n if (el) el.removeAttribute('src');\n setScreen(null);\n setFps(0);\n };\n }, [streamUrl, applyStreamSrc]);\n\n // ── Helper control WebSocket (touch/buttons out, screen config in) ──\n const wsUrl = config?.wsUrl ?? null;\n useEffect(() => {\n setHardwareKeyboardConnectedState(null);\n if (!wsUrl) return;\n let cancelled = false;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n hasWsConfigRef.current = false;\n\n const connect = () => {\n if (cancelled) return;\n let ws: WebSocket;\n try {\n ws = new WebSocket(wsUrl, socketProtocols);\n } catch {\n return;\n }\n ws.binaryType = 'arraybuffer';\n wsRef.current = ws;\n ws.onopen = () => {\n // Deliver whatever the user did while the socket was down.\n pendingWsRef.current = flushWsMessageQueue(ws, pendingWsRef.current);\n // The Hub owns keyboard forwarding while this socket is active. Keep the\n // Simulator's separate host-keyboard connection off so iOS shows its\n // software keyboard while browser HID keys continue to type. serve-sim\n // reconnects it once the last input socket detaches.\n sendWs(WS_MSG_HARDWARE_KEYBOARD, { enabled: false });\n if (!cancelled) setHardwareKeyboardConnectedState(false);\n };\n ws.onmessage = (event) => {\n if (!(event.data instanceof ArrayBuffer)) return;\n const bytes = new Uint8Array(event.data);\n if (bytes.length < 1 || bytes[0] !== WS_TAG_SCREEN_CONFIG) return;\n try {\n const c = JSON.parse(decoder.decode(bytes.subarray(1))) as ScreenSize;\n if (c.width > 0 && c.height > 0) {\n hasWsConfigRef.current = true;\n setScreen((prev) =>\n prev &&\n prev.width === c.width &&\n prev.height === c.height &&\n prev.orientation === c.orientation\n ? prev\n : c,\n );\n }\n } catch {}\n };\n ws.onclose = () => {\n if (cancelled) return;\n wsRef.current = null;\n retryTimer = setTimeout(connect, RECONNECT_MS);\n };\n ws.onerror = () => {\n try {\n ws.close();\n } catch {}\n };\n };\n connect();\n\n return () => {\n cancelled = true;\n if (retryTimer) clearTimeout(retryTimer);\n try {\n wsRef.current?.close();\n } catch {}\n wsRef.current = null;\n // Queued input was for this device; don't replay it on the next one.\n pendingWsRef.current = [];\n setHardwareKeyboardConnectedState(null);\n };\n }, [wsUrl, sendWs, socketProtocols]);\n\n // ── Long-lived middleware SSE routes multiplexed over one authenticated\n // exec-ws, matching serve-sim's browser client. Keeping logs, events, and\n // metrics off separate HTTP streams avoids the per-origin connection cap. ──\n const execWsUrl = config?.execWsUrl ?? null;\n const execToken = config?.execToken ?? null;\n const logsPath = config?.logsPath ?? null;\n const eventsPath = config?.eventsPath ?? null;\n const metricsPath = config?.metricsPath ?? null;\n const deviceUdid = config?.device ?? null;\n const axUrl = config?.axUrl ?? null;\n\n const accessibilityLoader = useMemo<AccessibilityLoader | null>(\n () => (axUrl ? (signal) => loadIosAccessibility(axUrl, signal, sessionFetch) : null),\n [axUrl, sessionFetch],\n );\n const accessibilityState = useAccessibility(accessibilityLoader);\n\n // One-shot typed host actions (`location.*`, `app.*`) over the exec channel.\n const runAction = useMemo(\n () =>\n execWsUrl && execToken\n ? (action: string, params?: Parameters<typeof runHostAction>[3]) =>\n runHostAction(execWsUrl, execToken, action, params, socketProtocols)\n : null,\n [execWsUrl, execToken, socketProtocols],\n );\n\n const locationBackend = useMemo<DeviceLocationBackend | null>(() => {\n if (!runAction || !deviceUdid) return null;\n return {\n set: (fix) => setIosLocation(runAction, deviceUdid, fix),\n clear: () => clearIosLocation(runAction, deviceUdid),\n };\n }, [runAction, deviceUdid]);\n const {\n location,\n locationPending,\n locationError,\n setLocation,\n clearLocation,\n locationCapabilities,\n } = useDeviceLocation(locationBackend);\n\n // serve-sim exposes permissions over its CLI channel only, so the Hub has no\n // route to read them. The section stays hidden until serve-sim serves them.\n const appPermissions = useAppPermissions({ active, appId: null, backend: null });\n\n useEffect(() => {\n setEventLogState(createIosEventLogState());\n }, [eventsPath, deviceUdid]);\n\n useEffect(() => {\n activityLastSampleAtRef.current = 0;\n if (!metricsPath) {\n setActivity(null);\n return;\n }\n setActivity({ hostCores: null, samples: [], errored: false, stale: false });\n const watchdog = setInterval(() => {\n const lastSampleAt = activityLastSampleAtRef.current;\n if (lastSampleAt > 0 && Date.now() - lastSampleAt > ACTIVITY_STALE_MS) {\n setActivity((current) => (current && !current.stale ? { ...current, stale: true } : current));\n }\n }, 1000);\n return () => clearInterval(watchdog);\n }, [metricsPath]);\n\n useEffect(() => {\n if (!execWsUrl || !execToken) return;\n const subscriptions = new Map<number, 'logs' | 'events' | 'metrics'>();\n const paths = new Map<number, string>();\n if (logsEnabled && logsPath) {\n subscriptions.set(1, 'logs');\n paths.set(1, logsPath);\n }\n if (eventsEnabled && eventsPath && deviceUdid) {\n subscriptions.set(2, 'events');\n paths.set(2, eventsPath);\n }\n if (metricsPath) {\n subscriptions.set(3, 'metrics');\n paths.set(3, metricsPath);\n }\n if (subscriptions.size === 0) return;\n\n let cancelled = false;\n let ws: WebSocket | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n const buffers = new Map<number, string>();\n\n const markInterrupted = () => {\n if (metricsPath) {\n setActivity((current) => (current ? { ...current, errored: true } : current));\n }\n };\n\n const emit = (kind: 'logs' | 'events' | 'metrics', block: ParsedSseBlock) => {\n if (kind === 'logs') {\n let message = block.data;\n try {\n const parsed = JSON.parse(block.data) as { eventMessage?: string };\n if (typeof parsed.eventMessage === 'string') message = parsed.eventMessage;\n } catch {}\n if (message) {\n setLogs((previous) =>\n [\n ...previous,\n { id: `i${++logSeqRef.current}`, source: 'syslog', message },\n ].slice(-MAX_LOGS),\n );\n }\n return;\n }\n if (kind === 'events' && deviceUdid) {\n setEventLogState((current) =>\n mergeIosEventLogPayload(current, block.data, deviceUdid),\n );\n return;\n }\n if (kind !== 'metrics') return;\n try {\n const payload = JSON.parse(block.data) as unknown;\n if (block.event === 'meta') {\n const hostCores = parseActivityHostCores(payload);\n setActivity((current) =>\n current ? { ...current, hostCores, errored: false } : current,\n );\n return;\n }\n const sample = parseActivitySample(payload);\n if (!sample) return;\n activityLastSampleAtRef.current = Date.now();\n setActivity((current) =>\n appendActivitySample(\n current ?? { hostCores: null, samples: [], errored: false, stale: false },\n sample,\n ),\n );\n } catch {}\n };\n\n const connect = () => {\n if (cancelled) return;\n buffers.clear();\n try {\n ws = new WebSocket(execWsUrl, socketProtocols);\n } catch {\n markInterrupted();\n retryTimer = setTimeout(connect, RECONNECT_MS);\n return;\n }\n ws.onopen = () => ws?.send(JSON.stringify({ token: execToken }));\n ws.onmessage = (event) => {\n let msg: { ready?: boolean; sub?: number; data?: string; end?: boolean };\n try {\n msg = JSON.parse(String(event.data));\n } catch {\n return;\n }\n if (msg.ready) {\n for (const [sub, path] of paths) {\n ws?.send(JSON.stringify({ sub, path }));\n }\n return;\n }\n if (typeof msg.sub !== 'number' || !subscriptions.has(msg.sub)) return;\n if (msg.end) {\n markInterrupted();\n ws?.close();\n return;\n }\n if (typeof msg.data === 'string') {\n const sub = msg.sub;\n const kind = subscriptions.get(sub)!;\n buffers.set(\n sub,\n drainSseChunk(buffers.get(sub) ?? '', msg.data, (block) => emit(kind, block)),\n );\n }\n };\n ws.onclose = () => {\n if (!cancelled) {\n markInterrupted();\n retryTimer = setTimeout(connect, RECONNECT_MS);\n }\n };\n ws.onerror = () => {\n try {\n ws?.close();\n } catch {}\n };\n };\n connect();\n\n return () => {\n cancelled = true;\n if (retryTimer) clearTimeout(retryTimer);\n try {\n ws?.close();\n } catch {}\n };\n }, [\n logsEnabled,\n eventsEnabled,\n execWsUrl,\n execToken,\n logsPath,\n eventsPath,\n metricsPath,\n deviceUdid,\n socketProtocols,\n ]);\n\n // ── Simulator settings (best-effort) — one status request hydrates every\n // device-options control, including the appearance used by the toolbar. ──\n useEffect(() => {\n deviceSettingWriteTrackerRef.current.reset();\n setDeviceSettingsPending(new Set());\n if (!execWsUrl || !execToken || !deviceUdid) {\n setAppearanceState(null);\n setDeviceSettings(null);\n return;\n }\n let cancelled = false;\n hostUiRequest(execWsUrl, execToken, { device: deviceUdid }, socketProtocols)\n .then((res) => {\n if (cancelled) return;\n const next: DeviceSettings = {};\n for (const [key, value] of Object.entries(res.status ?? {})) {\n if (typeof value === 'string') next[key as DeviceSettingKey] = value;\n }\n setDeviceSettings(next);\n if (next.appearance === 'light' || next.appearance === 'dark') {\n setAppearanceState(next.appearance);\n }\n // The helper socket's open handler usually settles this first (it\n // disconnects the hardware keyboard); only fill in an unknown.\n const keyboardValue = res.status?.[UI_OPTION_HARDWARE_KEYBOARD];\n if (keyboardValue === 'on' || keyboardValue === 'off') {\n setHardwareKeyboardConnectedState((prev) => prev ?? keyboardValue === 'on');\n }\n })\n .catch(() => {\n /* unreachable / unsupported — leave unknown */\n });\n return () => {\n cancelled = true;\n };\n }, [execWsUrl, execToken, deviceUdid, socketProtocols]);\n\n // ── Runtime encoder settings (serve-sim helper GET/PATCH endpoint) ──\n const streamSettingsUrl = config?.streamSettingsUrl ?? null;\n const initialStreamSettings = config?.initialStreamSettings;\n const normalizedInitialStreamSettings = useMemo(\n () => (streamSettingsUrl ? normalizeDeviceStreamSettings(initialStreamSettings) : null),\n [initialStreamSettings, streamSettingsUrl],\n );\n const { streamSettings, streamSettingsPending, updateStreamSettings } = useStreamSettingsResource(\n {\n url: streamSettingsUrl,\n initialSettings: normalizedInitialStreamSettings,\n parse: parseIosStreamSettings,\n toPatch: iosStreamSettingsPatch,\n fetchImpl: sessionFetch,\n },\n );\n\n // ── Foreground app (middleware /appstate SSE) — the middleware bootstraps a\n // fresh subscriber with the current frontmost app, then pushes changes as\n // SpringBoard foregrounds apps. EventSource reconnects on its own. ──\n const appStateUrl = config?.appStateUrl ?? null;\n useEffect(() => {\n setForegroundApp(null);\n if (!appStateUrl) return;\n let source: EventSource | null = null;\n try {\n source = new EventSource(appStateUrl);\n } catch {\n return;\n }\n source.onmessage = (event) => {\n try {\n const data = JSON.parse(String(event.data)) as {\n bundleId?: string;\n pid?: number;\n isReactNative?: boolean;\n };\n if (data.bundleId) {\n // Merge repeat events for the same app so a relaunch (new pid)\n // doesn't wipe the bundle details filled in below.\n setForegroundApp((prev) =>\n prev && prev.id === data.bundleId\n ? prev.pid === data.pid && prev.isReactNative === data.isReactNative\n ? prev\n : { ...prev, pid: data.pid, isReactNative: data.isReactNative }\n : { id: data.bundleId!, pid: data.pid, isReactNative: data.isReactNative },\n );\n }\n } catch {}\n };\n return () => source?.close();\n }, [appStateUrl]);\n\n // ── Foreground app details (name, versions, icon) — introspected from the\n // app bundle on the host over exec-ws whenever the foreground bundle id\n // changes. Cached per udid:bundleId, so revisits apply instantly. ──\n const foregroundAppId = foregroundApp?.id ?? null;\n useEffect(() => {\n if (!foregroundAppId || !runAction || !deviceUdid) return;\n let cancelled = false;\n getIosAppDetails(runAction, deviceUdid, foregroundAppId)\n .then((details) => {\n if (cancelled || !details) return;\n setForegroundApp((prev) =>\n prev && prev.id === foregroundAppId ? { ...prev, ...details } : prev,\n );\n })\n .catch(() => {\n /* exec channel unavailable — the id/pid line still renders */\n });\n return () => {\n cancelled = true;\n };\n }, [foregroundAppId, runAction, deviceUdid]);\n\n // ── Running simulators (middleware /grid/api) ──\n const gridApiUrl = config?.gridApiUrl ?? null;\n useEffect(() => {\n if (!gridApiUrl) {\n setDevices(PLACEHOLDER_DEVICES);\n return;\n }\n let cancelled = false;\n sessionFetch(gridApiUrl, { signal: AbortSignal.timeout(3000) })\n .then((r) => r.json())\n .then((data: { devices?: Array<Record<string, unknown>> }) => {\n if (cancelled || !Array.isArray(data.devices) || data.devices.length === 0) return;\n setDevices(\n data.devices.map((d) => ({\n id: String(d.device ?? d.id ?? 'ios'),\n name: String(d.name ?? d.device ?? 'Simulator'),\n system: typeof d.runtime === 'string' ? d.runtime : undefined,\n platform: 'ios' as const,\n current: d.helper != null,\n })),\n );\n })\n .catch(() => {\n /* unreachable — keep the placeholder */\n });\n return () => {\n cancelled = true;\n };\n }, [gridApiUrl, sessionFetch]);\n\n const deviceSettingsAvailable = !!execWsUrl && !!execToken && !!deviceUdid;\n const activityAvailable = !!metricsPath;\n const eventsAvailable = !!eventsPath;\n const accessibilityAvailable = accessibilityLoader !== null;\n const streamSettingsAvailable = !!streamSettingsUrl;\n const capabilities = useMemo<DeviceCapabilities>(\n () => ({\n deviceSettings: deviceSettingsAvailable,\n activity: activityAvailable,\n events: eventsAvailable,\n camera: false,\n accessibility: accessibilityAvailable,\n streamSettings: streamSettingsAvailable ? IOS_STREAM_SETTING_CAPABILITIES : false,\n location: locationCapabilities,\n permissions: false,\n }),\n [\n deviceSettingsAvailable,\n activityAvailable,\n eventsAvailable,\n accessibilityAvailable,\n streamSettingsAvailable,\n locationCapabilities,\n ],\n );\n\n return {\n platform: 'ios',\n status,\n error,\n screen,\n fps,\n devices,\n logs,\n logsEnabled,\n attachLogs,\n detachLogs,\n clearLogs,\n events,\n eventsEnabled,\n attachEvents,\n detachEvents,\n clearEvents,\n activity,\n deviceSettings,\n deviceSettingsPending,\n setDeviceSetting,\n displayWidthDp: null,\n camera: null,\n cameraPending: NO_PENDING_CAMERA_WRITES,\n cameraError: null,\n setCameraImage: noop,\n clearCameraImage: noop,\n ...accessibilityState,\n location,\n locationPending,\n locationError,\n setLocation,\n clearLocation,\n ...appPermissions,\n streamCapabilities: IOS_STREAM_CAPABILITIES,\n screenRecording: null,\n streamSettings,\n streamSettingsPending,\n updateStreamSettings,\n streamSource: null,\n streamSourcePending: false,\n streamSourceError: null,\n setStreamSource: noop,\n setGrpcImageMode: noop,\n setGrpcEncoder: noop,\n setGrpcInputSource: noop,\n streamStats,\n setStreamStatsEnabled,\n webRtcCodec,\n setWebRtcCodec,\n capabilities,\n foregroundApp,\n videoKind: useWebRtc ? 'video' : useAvcc ? 'canvas' : 'img',\n attachVideo,\n sendTouch,\n sendMultiTouch,\n sendKey,\n sendKeyEvents,\n sendScroll,\n pressButton,\n reload,\n rotate,\n screenshot,\n appearance,\n setAppearance,\n hardwareKeyboardConnected,\n setHardwareKeyboardConnected,\n toggleSoftwareKeyboard,\n };\n}\n"],"names":["useCallback","useEffect","useLayoutEffect","useMemo","useReducer","useRef","useState","AVCC_FRAME_TIMEOUT_MS","avccFallbackReducer","initialAvccFallback","appendActivitySample","parseActivityHostCores","parseActivitySample","loadIosAccessibility","isAvccSupported","HID_EDGE_BOTTOM","homeIndicatorEdge","rawDeltaForDisplayDelta","rawEdgeForDisplayEdge","rawPointForDisplayPoint","streamGeometry","startIosHelper","clearIosEventLogState","createIosEventLogState","mergeIosEventLogPayload","hostUiRequest","runHostAction","getIosAppDetails","clearIosLocation","setIosLocation","fetchScreenshot","hidUsageForCode","NO_PENDING_CAMERA_WRITES","mergeAuthoritativeDeviceSetting","KeyedWriteTracker","createPacedKeySender","middlewareEndpointForBrowser","proxyPreviewConfigForBrowser","sessionTokenFetch","sessionTokenProtocols","withSessionTokenQuery","drainSseChunk","normalizeDeviceStreamSettings","useAccessibility","useAppPermissions","useAvccStream","useDeviceLocation","useStreamSettingsResource","useWebRtcStream","presentedVideoFrameDelta","webRtcFallbackDecision","flushWsMessageQueue","sendOrQueueWsMessage","MAX_LOGS","RECONNECT_MS","ACTIVITY_STALE_MS","noop","WS_MSG_TOUCH","WS_MSG_BUTTON","WS_MSG_MULTI_TOUCH","WS_MSG_KEY","WS_MSG_ORIENTATION","WS_MSG_SCROLL","WS_MSG_SOFTWARE_KEYBOARD","WS_MSG_HARDWARE_KEYBOARD","WS_TAG_SCREEN_CONFIG","HID_USAGE_R","UI_OPTION_HARDWARE_KEYBOARD","PLACEHOLDER_DEVICES","id","name","platform","current","IOS_STREAM_CAPABILITIES","modeAvailability","mjpeg","h264","webrtc","httpCodecs","webRtcCodecs","IOS_STREAM_SETTING_CAPABILITIES","mjpegFps","mjpegQuality","maxDimension","h264Bitrate","h264Fps","ORIENTATION_CYCLE","BUTTON_NAME","home","appSwitcher","power","back","recents","hideKeyboard","decoder","TextDecoder","parseIosStreamSettings","value","fallback","iosStreamSettingsPatch","patch","Object","keys","length","toWs","url","replace","toQueryStyleHelperWsUrl","wsUrl","URL","match","pathname","Error","searchParams","has","set","decodeURIComponent","toString","connectionKey","baseUrl","device","token","JSON","stringify","useIosDeviceClient","options","enabled","targetDevice","streamMode","active","sessionFetch","socketProtocols","status","setStatus","error","setError","screen","setScreen","fps","setFps","logs","setLogs","logsEnabled","setLogsEnabled","eventLogState","setEventLogState","events","eventsEnabled","setEventsEnabled","activity","setActivity","devices","setDevices","resolvedConfig","setResolvedConfig","config","key","appearance","setAppearanceState","deviceSettings","setDeviceSettings","deviceSettingsPending","setDeviceSettingsPending","Set","hardwareKeyboardConnected","setHardwareKeyboardConnectedState","foregroundApp","setForegroundApp","wsRef","pendingWsRef","logSeqRef","imgRef","canvasRef","videoRef","streamUrlRef","avccFallback","dispatchAvccFallback","webRtcCodec","setWebRtcCodecState","activeWebRtcCodec","setActiveWebRtcCodec","webRtcHttpFallback","setWebRtcHttpFallback","deviceSettingWriteTrackerRef","deviceSettingConfigRef","activityLastSampleAtRef","useWebRtc","wantsAvcc","useAvcc","fellBack","edgeGestureRef","screenRef","hasWsConfigRef","applyStreamSrc","img","src","includes","Date","now","attachVideo","el","sendWs","tag","payload","sendTouch","sample","orientation","inputOrientation","displayEdge","phase","undefined","p","x","y","edge","type","sendMultiTouch","a","b","x1","y1","x2","y2","sendScroll","Number","isFinite","dx","dy","delta","anchor","sendKey","input","usage","code","keySender","event","dispose","sendKeyEvents","enqueue","setHardwareKeyboardConnected","connected","c","execWsUrl","execToken","previous","option","catch","toggleSoftwareKeyboard","pressButton","button","reload","Promise","r","setTimeout","rotate","next","indexOf","screenshot","udid","setDeviceSetting","tracker","request","start","pending","isCurrent","result","authoritative","nextKey","nextValue","entries","nextAppearance","finally","finish","setAppearance","mode","setWebRtcCodec","codec","attachLogs","detachLogs","clearLogs","attachEvents","detachEvents","clearEvents","cancelled","pollTimer","apiUrl","encodeURIComponent","toMiddleware","rawConfig","middlewareUrl","window","location","href","basePath","absoluteMiddlewareUrl","path","proxyHelpers","appStateUrl","appStateEndpoint","streamUrl","logsPath","logsEndpoint","eventsPath","eventLogEventsEndpoint","metricsPath","metricsEndpoint","axUrl","axEndpoint","streamSettingsUrl","streamSettingsEndpoint","initialStreamSettings","streamSettings","gridApiUrl","gridApiEndpoint","transport","iceServers","webRtcIceServers","startRequested","resolve","res","signal","AbortSignal","timeout","ok","json","resolved","clearTimeout","fpsCounterRef","frames","startedAt","onAvccFrame","frameDelta","performance","counter","Math","round","stream","webRtcStream","failure","webRtcFailure","webRtcError","markFrameDecoded","markWebRtcFrameDecoded","streamStats","setStreamStatsEnabled","offerUrl","closeUrl","closeBeaconUrl","statsUrl","fetchImpl","handledWebRtcFailureRef","sessionId","decision","video","stopped","firstFrame","frameCallback","previousPresentedFrames","markFrame","presentedFrameDelta","videoWidth","width","videoHeight","height","prev","onVideoFrame","_now","metadata","presentedFrames","isSafeInteger","requestVideoFrameCallback","onTimeUpdate","onLoadedData","srcObject","supportsVideoFrameCallback","addEventListener","once","play","removeEventListener","cancelVideoFrameCallback","timer","onFirstFrame","onFrame","onDecodedFrame","onResize","onError","message","onDecoderError","settled","retryTimer","markStreaming","naturalWidth","naturalHeight","poll","setInterval","clearInterval","removeAttribute","connect","ws","WebSocket","binaryType","onopen","onmessage","data","ArrayBuffer","bytes","Uint8Array","parse","decode","subarray","onclose","onerror","close","deviceUdid","accessibilityLoader","accessibilityState","runAction","action","params","locationBackend","fix","clear","locationPending","locationError","setLocation","clearLocation","locationCapabilities","appPermissions","appId","backend","hostCores","samples","errored","stale","watchdog","lastSampleAt","subscriptions","Map","paths","size","buffers","markInterrupted","emit","kind","block","parsed","eventMessage","source","slice","send","msg","String","ready","sub","end","get","reset","then","keyboardValue","normalizedInitialStreamSettings","streamSettingsPending","updateStreamSettings","initialSettings","toPatch","EventSource","bundleId","pid","isReactNative","foregroundAppId","details","Array","isArray","map","d","system","runtime","helper","deviceSettingsAvailable","activityAvailable","eventsAvailable","accessibilityAvailable","streamSettingsAvailable","capabilities","camera","accessibility","permissions","displayWidthDp","cameraPending","cameraError","setCameraImage","clearCameraImage","streamCapabilities","screenRecording","streamSource","streamSourcePending","streamSourceError","setStreamSource","setGrpcImageMode","setGrpcEncoder","setGrpcInputSource","videoKind"],"mappings":"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yBAAyBlE,wBAAwB;IACvD,MAAMmE,0BAA0B,CAAC,CAAC7K;IAClC,MAAM8K,eAAe/d,QACnB,IAAO,CAAA;YACL4J,gBAAgB8T;YAChBxU,UAAUyU;YACV5U,QAAQ6U;YACRI,QAAQ;YACRC,eAAeJ;YACfzK,gBAAgB0K,0BAA0BjZ,kCAAkC;YAC5EmN,UAAUuI;YACV2D,aAAa;QACf,CAAA,GACA;QACER;QACAC;QACAC;QACAC;QACAC;QACAvD;KACD;IAGH,OAAO;QACLnW,UAAU;QACV6D;QACAE;QACAE;QACAE;QACAa;QACAX;QACAE;QACAuI;QACAC;QACAC;QACArI;QACAC;QACAqI;QACAC;QACAC;QACArI;QACAU;QACAE;QACAkG;QACAmO,gBAAgB;QAChBH,QAAQ;QACRI,eAAevc;QACfwc,aAAa;QACbC,gBAAgBjb;QAChBkb,kBAAkBlb;QAClB,GAAGuW,kBAAkB;QACrB5H;QACAmI;QACAC;QACAC;QACAC;QACA,GAAGE,cAAc;QACjBgE,oBAAoBla;QACpBma,iBAAiB;QACjBrL;QACAqJ;QACAC;QACAgC,cAAc;QACdC,qBAAqB;QACrBC,mBAAmB;QACnBC,iBAAiBxb;QACjByb,kBAAkBzb;QAClB0b,gBAAgB1b;QAChB2b,oBAAoB3b;QACpB+R;QACAC;QACAvK;QACAkG;QACA+M;QACA5T;QACA8U,WAAW1T,YAAY,UAAUE,UAAU,WAAW;QACtDW;QACAK;QACAY;QACAc;QACAO;QACAd;QACAyB;QACAE;QACAI;QACAG;QACApG;QACAoH;QACA7G;QACA2E;QACAQ;IACF;AACF"}
1
+ {"version":3,"sources":["../src/useIosDevice.ts"],"sourcesContent":["/**\n * serve-sim (iOS) implementation of the {@link DeviceClient} interface.\n *\n * Mirrors the serve-sim web client's architecture: the entry point is the\n * serve-sim **middleware** (default `:3200`), not the bare streaming helper.\n *\n * 1. `GET <base>/api` → the live config: the helper `url`/`streamUrl`/`wsUrl`,\n * the `device` udid, the per-session `execToken`, and the `logsEndpoint` /\n * `gridApiEndpoint` route paths.\n * 2. Video: MJPEG `<img>` from the helper's `streamUrl`. Input + screen config:\n * the helper's binary WebSocket (`0x03` touch, `0x04` button, `0x05`\n * multi-touch, `0x06` key, `0x0b` scroll, `0x0e` hardware keyboard out;\n * `0x82` screen config in). Coordinates are mapped to the device's raw\n * frame per orientation (see `./orientation`). Input sent while the socket\n * is reconnecting is queued briefly (see `./ws-send-queue`).\n * 3. Logs: streamed over the middleware's **exec-ws** WebSocket exactly like\n * the serve-sim client — `{token}` → `{sub, path: logsEndpoint}` → `{sub,\n * data}` (raw SSE) — rather than a direct route on the helper (the helper\n * has none). One-shot host actions (`{id, action, params}`) and\n * simulator-settings requests (`{id, ui}`) share that channel (`./exec-ws`).\n * 4. Devices: `GET <base>/grid/api`.\n *\n * `baseUrl` is always the mounted serve-sim middleware. Connection failures\n * keep retrying middleware discovery; they must not be reinterpreted as a bare\n * helper because doing so drops the middleware/helper path from stream URLs.\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';\n\nimport { AVCC_FRAME_TIMEOUT_MS, avccFallbackReducer, initialAvccFallback } from './avcc-fallback';\nimport {\n appendActivitySample,\n parseActivityHostCores,\n parseActivitySample,\n} from './activity';\nimport { type AccessibilityLoader, loadIosAccessibility } from './accessibility';\nimport { isAvccSupported } from './avcc';\nimport {\n HID_EDGE_BOTTOM,\n homeIndicatorEdge,\n rawDeltaForDisplayDelta,\n rawEdgeForDisplayEdge,\n rawPointForDisplayPoint,\n streamGeometry,\n} from './orientation';\nimport { startIosHelper } from './connections';\nimport {\n clearIosEventLogState,\n createIosEventLogState,\n mergeIosEventLogPayload,\n} from './ios-events';\nimport { hostUiRequest, runHostAction } from './exec-ws';\nimport { fetchIosAppIcon, getIosAppDetails } from './ios-app-details';\nimport { clearIosLocation, setIosLocation } from './ios-location';\nimport { fetchScreenshot } from './screenshot';\nimport { hidUsageForCode } from './keyboard';\nimport {\n type ConnectionStatus,\n type DeviceActivity,\n type DeviceAppearance,\n type DeviceClient,\n type DeviceCapabilities,\n type DeviceConnectionOptions,\n type DeviceLog,\n type DeviceSettingKey,\n type DeviceSettings,\n type DeviceStreamCapabilities,\n type DeviceStreamEncoderSettings,\n type DeviceStreamSettingCapabilities,\n type DeviceWebRtcCodec,\n type DeviceOrientation,\n type ForegroundApp,\n type HardwareButton,\n type HidKeyEvent,\n type KeyboardInput,\n type MultiTouchSample,\n type RunningDevice,\n type ScreenSize,\n type ScreenshotCapture,\n type ScrollSample,\n type TouchSample,\n} from './types';\nimport { NO_PENDING_CAMERA_WRITES } from './device-camera';\nimport { mergeAuthoritativeDeviceSetting } from './device-setting-writes';\nimport { KeyedWriteTracker } from './keyed-write-tracker';\nimport { createPacedKeySender } from './paced-key-sender';\nimport { middlewareEndpointForBrowser, proxyPreviewConfigForBrowser } from './proxy-preview-config';\nimport { sessionTokenFetch, sessionTokenProtocols, withSessionTokenQuery } from './session-token';\nimport { type ParsedSseBlock, drainSseChunk } from './sse';\nimport { normalizeDeviceStreamSettings } from './stream-settings';\nimport { useAccessibility } from './useAccessibility';\nimport { useAppPermissions } from './useAppPermissions';\nimport { useAvccStream } from './useAvccStream';\nimport { type DeviceLocationBackend, useDeviceLocation } from './useDeviceLocation';\nimport { useStreamSettingsResource } from './useStreamSettingsResource';\nimport { useWebRtcStream, type WebRtcIceServer } from './useWebRtcStream';\nimport { presentedVideoFrameDelta } from './video-frame-metadata';\nimport {\n type WebRtcCodec,\n webRtcFallbackDecision,\n} from './webrtc-fallback';\nimport {\n flushWsMessageQueue,\n type QueuedWsMessage,\n sendOrQueueWsMessage,\n} from './ws-send-queue';\n\nconst MAX_LOGS = 200;\nconst RECONNECT_MS = 1500;\nconst ACTIVITY_STALE_MS = 8000;\nconst noop = () => {};\n\n// serve-sim binary WS message tags (serve-sim-client `SimulatorView`).\nconst WS_MSG_TOUCH = 0x03;\nconst WS_MSG_BUTTON = 0x04;\nconst WS_MSG_MULTI_TOUCH = 0x05;\nconst WS_MSG_KEY = 0x06;\nconst WS_MSG_ORIENTATION = 0x07;\n// Native scroll (wheel/trackpad) in raw device fractions, anchored under the pointer.\nconst WS_MSG_SCROLL = 0x0b;\nconst WS_MSG_SOFTWARE_KEYBOARD = 0x0c;\n// Connect/disconnect the guest's hardware keyboard; serve-sim's own touch\n// client sends this too so the on-screen keyboard shows.\nconst WS_MSG_HARDWARE_KEYBOARD = 0x0e;\nexport const WS_TAG_SCREEN_CONFIG = 0x82;\n\n// HID keyboard usage codes (USB HID Usage Page 0x07) for the R reload chord.\nconst HID_USAGE_R = 0x15; // 'r'\n\n// The simulator-settings option behind `hardwareKeyboardConnected`.\nconst UI_OPTION_HARDWARE_KEYBOARD = 'hardware-keyboard';\n\nconst PLACEHOLDER_DEVICES: RunningDevice[] = [\n { id: 'ios', name: 'iPhone Simulator', platform: 'ios', current: true },\n];\n\nconst IOS_STREAM_CAPABILITIES = {\n modeAvailability: { mjpeg: true, h264: true, webrtc: true },\n httpCodecs: ['auto', 'h264', 'mjpeg'],\n webRtcCodecs: ['h264', 'vp9', 'vp8'],\n} as const satisfies DeviceStreamCapabilities;\n\nconst IOS_STREAM_SETTING_CAPABILITIES = {\n mjpegFps: true,\n mjpegQuality: true,\n maxDimension: true,\n h264Bitrate: true,\n h264Fps: true,\n} as const satisfies DeviceStreamSettingCapabilities;\n\n// The counterclockwise rotation order (matches Simulator's \"Rotate Left\"): each\n// press advances one step, so four presses come back around to portrait.\nconst ORIENTATION_CYCLE: DeviceOrientation[] = [\n 'portrait',\n 'landscape_left',\n 'portrait_upside_down',\n 'landscape_right',\n];\n\n// iOS only has a Home button + app switcher; the rest are no-ops.\nconst BUTTON_NAME: Record<HardwareButton, string | null> = {\n home: 'home',\n appSwitcher: 'app_switcher',\n power: 'lock',\n back: null,\n recents: null,\n hideKeyboard: null,\n};\n\nconst decoder = new TextDecoder();\n\nfunction parseIosStreamSettings(\n value: unknown,\n fallback: DeviceStreamEncoderSettings,\n): DeviceStreamEncoderSettings {\n return normalizeDeviceStreamSettings(value, fallback);\n}\n\nfunction iosStreamSettingsPatch(\n patch: Partial<DeviceStreamEncoderSettings>,\n): Partial<DeviceStreamEncoderSettings> | null {\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction toWs(url: string): string {\n return url.replace(/^http/, 'ws');\n}\n\n/**\n * `…/helper/<udid>/ws` -> `…/helper/ws?device=<udid>`\n * serve-sim\n */\nexport function toQueryStyleHelperWsUrl(wsUrl: string): string {\n const url = new URL(wsUrl);\n const match = url.pathname.match(/^(.*\\/helper)\\/([^/]+)\\/ws$/);\n if (!match) throw new Error(`Invalid helper ws url, no deviceId matched: ${wsUrl}`);\n url.pathname = `${match[1]}/ws`;\n if (!url.searchParams.has('device')) {\n url.searchParams.set('device', decodeURIComponent(match[2]));\n }\n return url.toString();\n}\n\n/** Resolved connection: where to stream video/input, and how to reach logs/devices. */\ninterface ResolvedConfig {\n /** Base serve-sim helper URL used by `/stream.avcc`. */\n url: string;\n streamUrl: string;\n wsUrl: string;\n device: string | null;\n /** Middleware exec-ws URL used for logs, events, metrics, and UI requests. */\n execWsUrl: string | null;\n execToken: string | null;\n /** Relative SSE path to subscribe for logs, e.g. `/logs?device=<udid>`. */\n logsPath: string | null;\n /** Absolute URL of the foreground-app SSE stream. */\n appStateUrl: string | null;\n /** Absolute URL of the app icon route, when the server has one. */\n appIconUrl: string | null;\n /** Relative SSE path for normalized serve-sim events. */\n eventsPath: string | null;\n /** Relative SSE path for foreground app activity. */\n metricsPath: string | null;\n axUrl: string | null;\n /** Runtime encoder settings endpoint on the selected helper. */\n streamSettingsUrl: string | null;\n /** Initial server-provided stream settings, if present. */\n initialStreamSettings: unknown;\n gridApiUrl: string | null;\n webRtcCodec: WebRtcCodec;\n webRtcIceServers?: WebRtcIceServer[];\n}\n\n/** Shape of the serve-sim middleware `/api` (and grid) responses we read. */\ninterface PreviewApi {\n url?: string;\n streamUrl?: string;\n wsUrl?: string;\n device?: string;\n basePath?: string;\n execToken?: string;\n logsEndpoint?: string;\n appStateEndpoint?: string;\n appIconEndpoint?: string;\n eventLogEventsEndpoint?: string;\n metricsEndpoint?: string;\n axEndpoint?: string;\n streamSettingsEndpoint?: string;\n gridApiEndpoint?: string;\n proxyHelpers?: boolean;\n streamSettings?:\n | ({ transport: 'http'; codec?: 'auto' | 'h264' | 'mjpeg' } &\n Partial<DeviceStreamEncoderSettings>)\n | ({ transport: 'webrtc'; codec: WebRtcCodec; iceServers?: WebRtcIceServer[] } &\n Partial<DeviceStreamEncoderSettings>);\n}\n\n// The server, device, and token that one resolved config belongs to.\nfunction connectionKey(baseUrl: string, device: string | null, token: string | null): string {\n return JSON.stringify([baseUrl, device, token]);\n}\n\n/** @deprecated Use DeviceClientProvider with useDeviceClient or useDeviceScreenClient instead. */\nexport function useIosDeviceClient(options: DeviceConnectionOptions): DeviceClient {\n const { baseUrl, enabled = true, device: targetDevice = null, streamMode, token = null } = options;\n const active = enabled && !!baseUrl;\n const sessionFetch = useMemo(() => sessionTokenFetch(token), [token]);\n const socketProtocols = useMemo(() => sessionTokenProtocols('ios', token), [token]);\n\n const [status, setStatus] = useState<ConnectionStatus>('idle');\n const [error, setError] = useState<string | null>(null);\n const [screen, setScreen] = useState<ScreenSize | null>(null);\n const [fps, setFps] = useState(0);\n const [logs, setLogs] = useState<DeviceLog[]>([]);\n // Logs are opt-in: nothing streams until the user attaches.\n const [logsEnabled, setLogsEnabled] = useState(false);\n const [eventLogState, setEventLogState] = useState(createIosEventLogState);\n const events = eventLogState.events;\n const [eventsEnabled, setEventsEnabled] = useState(false);\n const [activity, setActivity] = useState<DeviceActivity | null>(null);\n const [devices, setDevices] = useState<RunningDevice[]>(PLACEHOLDER_DEVICES);\n // The credentials above follow the options at once, but a new config waits for `/api`. Until\n // it arrives, the old config is not used, so its URLs never get another connection's token.\n // Effect cleanups still close the old connections with the credentials they opened them with.\n const [resolvedConfig, setResolvedConfig] = useState<{\n key: string;\n config: ResolvedConfig;\n } | null>(null);\n const config =\n active && baseUrl && resolvedConfig?.key === connectionKey(baseUrl, targetDevice, token)\n ? resolvedConfig.config\n : null;\n // The simulator's system dark/light setting. null until read.\n const [appearance, setAppearanceState] = useState<DeviceAppearance | null>(null);\n const [deviceSettings, setDeviceSettings] = useState<DeviceSettings | null>(null);\n const [deviceSettingsPending, setDeviceSettingsPending] = useState<\n ReadonlySet<DeviceSettingKey>\n >(() => new Set());\n // Browser HID injection remains active when this is false. Disabling the\n // Simulator-owned host connection lets iOS keep its software keyboard open.\n const [hardwareKeyboardConnected, setHardwareKeyboardConnectedState] = useState<boolean | null>(\n null,\n );\n // The frontmost app, pushed by the middleware's /appstate SSE. null until the\n // first event.\n const [foregroundApp, setForegroundApp] = useState<ForegroundApp | null>(null);\n\n const wsRef = useRef<WebSocket | null>(null);\n // Input that arrived while the helper socket was down; flushed on reconnect\n // (bounded, and stale entries are dropped — see `./ws-send-queue`).\n const pendingWsRef = useRef<QueuedWsMessage[]>([]);\n // Monotonic log id source, persisted across log-stream reconnects so ids stay\n // unique even though lines are kept (the stream effect may re-run).\n const logSeqRef = useRef(0);\n const imgRef = useRef<HTMLImageElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const streamUrlRef = useRef<string | null>(null);\n const [avccFallback, dispatchAvccFallback] = useReducer(avccFallbackReducer, initialAvccFallback);\n const [webRtcCodec, setWebRtcCodecState] = useState<DeviceWebRtcCodec>('h264');\n const [activeWebRtcCodec, setActiveWebRtcCodec] = useState<WebRtcCodec>('h264');\n const [webRtcHttpFallback, setWebRtcHttpFallback] = useState(false);\n const deviceSettingWriteTrackerRef = useRef(new KeyedWriteTracker<DeviceSettingKey>());\n // Async option writes capture their config. Track only committed config so\n // an interrupted concurrent render cannot invalidate a legitimate rollback.\n const deviceSettingConfigRef = useRef(config);\n useLayoutEffect(() => {\n deviceSettingConfigRef.current = config;\n }, [config]);\n const activityLastSampleAtRef = useRef(0);\n const useWebRtc = streamMode === 'webrtc' && !webRtcHttpFallback;\n const wantsAvcc = streamMode === 'h264' || webRtcHttpFallback;\n const useAvcc = wantsAvcc && isAvccSupported() && !avccFallback.fellBack;\n useEffect(() => {\n if (streamMode === 'webrtc') return;\n setWebRtcHttpFallback(false);\n setActiveWebRtcCodec(webRtcCodec);\n }, [streamMode, webRtcCodec]);\n // True while the in-flight single-finger drag began in the home-indicator band.\n const edgeGestureRef = useRef(false);\n // Latest screen config, read by the (stable) input callbacks for orientation.\n const screenRef = useRef<ScreenSize | null>(null);\n useEffect(() => {\n screenRef.current = screen;\n }, [screen]);\n // Once the helper WS pushes a config, it owns dimensions+orientation.\n const hasWsConfigRef = useRef(false);\n\n const applyStreamSrc = useCallback(() => {\n const img = imgRef.current;\n const url = streamUrlRef.current;\n if (!img || !url) return;\n img.src = `${url}${url.includes('?') ? '&' : '?'}t=${Date.now()}`;\n }, []);\n\n const attachVideo = useCallback(\n (el: HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | null) => {\n if (useWebRtc) {\n videoRef.current = (el as HTMLVideoElement) ?? null;\n canvasRef.current = null;\n imgRef.current = null;\n } else if (useAvcc) {\n canvasRef.current = (el as HTMLCanvasElement) ?? null;\n videoRef.current = null;\n imgRef.current = null;\n } else {\n imgRef.current = (el as HTMLImageElement) ?? null;\n canvasRef.current = null;\n videoRef.current = null;\n if (el) applyStreamSrc();\n }\n },\n [applyStreamSrc, useAvcc, useWebRtc],\n );\n\n // Every helper-socket message goes through here so a brief reconnect queues\n // input instead of dropping it (matching serve-sim's client).\n const sendWs = useCallback((tag: number, payload: object) => {\n pendingWsRef.current = sendOrQueueWsMessage(wsRef.current, pendingWsRef.current, tag, payload);\n }, []);\n\n const sendTouch = useCallback((sample: TouchSample) => {\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n\n let displayEdge: number | undefined;\n if (sample.phase === 'begin') {\n edgeGestureRef.current = homeIndicatorEdge(sample) !== undefined;\n if (edgeGestureRef.current) displayEdge = HID_EDGE_BOTTOM;\n } else if (edgeGestureRef.current) {\n displayEdge = HID_EDGE_BOTTOM;\n if (sample.phase === 'end') edgeGestureRef.current = false;\n }\n\n const p = rawPointForDisplayPoint(orientation, sample.x, sample.y);\n const edge = displayEdge === undefined ? undefined : rawEdgeForDisplayEdge(orientation, displayEdge);\n const payload =\n edge === undefined ? { type: sample.phase, ...p } : { type: sample.phase, ...p, edge };\n sendWs(WS_MSG_TOUCH, payload);\n }, [sendWs]);\n\n const sendMultiTouch = useCallback(\n (sample: MultiTouchSample) => {\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n const a = rawPointForDisplayPoint(orientation, sample.a.x, sample.a.y);\n const b = rawPointForDisplayPoint(orientation, sample.b.x, sample.b.y);\n sendWs(WS_MSG_MULTI_TOUCH, { type: sample.phase, x1: a.x, y1: a.y, x2: b.x, y2: b.y });\n },\n [sendWs],\n );\n\n // Scroll-to-pan: forwarded as a native scroll event so iOS pans content\n // exactly as it would for a physical wheel — no synthesized finger drag.\n // Both the delta and the cursor anchor are rotated into raw device\n // orientation so scrolling tracks the visible content on rotated devices.\n const sendScroll = useCallback(\n (sample: ScrollSample) => {\n if (!Number.isFinite(sample.dx) || !Number.isFinite(sample.dy)) return;\n if (sample.dx === 0 && sample.dy === 0) return;\n const orientation = streamGeometry(screenRef.current).inputOrientation;\n const delta = rawDeltaForDisplayDelta(orientation, sample.dx, sample.dy);\n const anchor = rawPointForDisplayPoint(orientation, sample.x, sample.y);\n sendWs(WS_MSG_SCROLL, { dx: delta.dx, dy: delta.dy, x: anchor.x, y: anchor.y });\n },\n [sendWs],\n );\n\n const sendKey = useCallback(\n (input: KeyboardInput): boolean => {\n const usage = hidUsageForCode(input.code);\n if (usage === null) return false;\n sendWs(WS_MSG_KEY, { type: input.phase, usage });\n return true;\n },\n [sendWs],\n );\n\n // Pre-mapped key events (phone-keyboard capture) are paced a few ms apart so\n // iOS doesn't coalesce a pasted string into a couple of lost keystrokes.\n const keySender = useMemo(\n () =>\n createPacedKeySender((event) => sendWs(WS_MSG_KEY, { type: event.type, usage: event.usage })),\n [sendWs],\n );\n useEffect(() => () => keySender.dispose(), [keySender]);\n const sendKeyEvents = useCallback(\n (events: ReadonlyArray<HidKeyEvent>) => keySender.enqueue(events),\n [keySender],\n );\n\n // Connect/disconnect the Mac keyboard from the guest through serve-sim's\n // `hardware-keyboard` simulator setting (the same request its settings panel\n // makes). Optimistic; reverted if the middleware rejects it.\n const setHardwareKeyboardConnected = useCallback(\n (connected: boolean) => {\n const c = config;\n if (!c || !c.execWsUrl || !c.execToken || !c.device) return;\n const previous = hardwareKeyboardConnected;\n setHardwareKeyboardConnectedState(connected);\n void hostUiRequest(\n c.execWsUrl,\n c.execToken,\n {\n device: c.device,\n option: UI_OPTION_HARDWARE_KEYBOARD,\n value: connected ? 'on' : 'off',\n },\n socketProtocols,\n ).catch(() => setHardwareKeyboardConnectedState(previous));\n },\n [config, hardwareKeyboardConnected, socketProtocols],\n );\n\n const toggleSoftwareKeyboard = useCallback(() => {\n sendWs(WS_MSG_SOFTWARE_KEYBOARD, {});\n }, [sendWs]);\n\n const pressButton = useCallback(\n (button: HardwareButton) => {\n const name = BUTTON_NAME[button];\n if (name) sendWs(WS_MSG_BUTTON, { button: name });\n },\n [sendWs],\n );\n\n // Reload the RN/Expo bundle by injecting ⌘R over the helper's key channel\n // (tag 0x06 → HID keystroke) — RN registers ⌘R as its reload shortcut. Mirrors\n // the serve-sim web client's sequence exactly: ⌘ down, R down, R up, ⌘ up, with\n // a sequential 30ms await between each event (so the gaps can't compress under\n // timer jitter). Harmless if the foreground app isn't RN.\n const reload = useCallback(async () => {\n const key = (type: 'down' | 'up', usage: number) => sendWs(WS_MSG_KEY, { type, usage });\n key('down', HID_USAGE_R);\n await new Promise((r) => setTimeout(r, 30));\n key('up', HID_USAGE_R);\n }, [sendWs]);\n\n // Rotate one step counterclockwise from the last known orientation, over the\n // helper's orientation channel (tag 0x07 → HID orientation event). The helper\n // confirms by pushing an updated screen config, which keeps the cycle in sync.\n const rotate = useCallback(() => {\n const current = screenRef.current?.orientation ?? 'portrait';\n const next =\n ORIENTATION_CYCLE[(ORIENTATION_CYCLE.indexOf(current) + 1) % ORIENTATION_CYCLE.length];\n sendWs(WS_MSG_ORIENTATION, { orientation: next });\n }, [sendWs]);\n\n // serve-sim's middleware captures the sim via `simctl io <udid> screenshot`\n // and returns the PNG bytes. Use the resolved udid from `/api` (falling back\n // to the requested device); the middleware defaults to the booted sim if none.\n const screenshot = useCallback(async (): Promise<ScreenshotCapture | null> => {\n if (!baseUrl) return null;\n const udid = config?.device ?? targetDevice;\n return fetchScreenshot(baseUrl, udid, sessionFetch);\n }, [baseUrl, targetDevice, config, sessionFetch]);\n\n // Apply any serve-sim UI option over its authenticated exec-ws request\n // channel. The state is optimistic so the selected pill/switch responds at\n // once. Writes are serialized per option, while unrelated options can update\n // concurrently. A failed request re-reads only that option's authoritative\n // value so it cannot roll back another optimistic write.\n const setDeviceSetting = useCallback(\n (key: DeviceSettingKey, value: string) => {\n const c = config;\n if (!c?.execWsUrl || !c.execToken || !c.device) return;\n const { device, execToken, execWsUrl } = c;\n const tracker = deviceSettingWriteTrackerRef.current;\n const request = tracker.start(key);\n if (!request) return;\n setDeviceSettingsPending(tracker.pending);\n setDeviceSettings((current) => ({ ...(current ?? {}), [key]: value }));\n if (key === 'appearance' && (value === 'light' || value === 'dark')) {\n setAppearanceState(value);\n }\n void hostUiRequest(execWsUrl, execToken, { device, option: key, value }, socketProtocols)\n .catch(async () => {\n if (!tracker.isCurrent(request) || deviceSettingConfigRef.current !== c) return;\n try {\n const result = await hostUiRequest(execWsUrl, execToken, { device }, socketProtocols);\n if (!tracker.isCurrent(request) || deviceSettingConfigRef.current !== c) return;\n const authoritative: DeviceSettings = {};\n for (const [nextKey, nextValue] of Object.entries(result.status ?? {})) {\n if (typeof nextValue === 'string') {\n authoritative[nextKey as DeviceSettingKey] = nextValue;\n }\n }\n setDeviceSettings((current) =>\n mergeAuthoritativeDeviceSetting(current, key, authoritative),\n );\n if (key === 'appearance') {\n const nextAppearance = authoritative.appearance;\n if (nextAppearance === 'light' || nextAppearance === 'dark') {\n setAppearanceState(nextAppearance);\n }\n }\n } catch {\n // Keep the optimistic value if both the write and authoritative\n // refresh channels are temporarily unavailable.\n }\n })\n .finally(() => {\n if (tracker.finish(request)) setDeviceSettingsPending(tracker.pending);\n });\n },\n [config, socketProtocols],\n );\n\n const setAppearance = useCallback(\n (mode: DeviceAppearance) => setDeviceSetting('appearance', mode),\n [setDeviceSetting],\n );\n\n const setWebRtcCodec = useCallback((codec: DeviceWebRtcCodec) => {\n setWebRtcCodecState(codec);\n setActiveWebRtcCodec(codec);\n setWebRtcHttpFallback(false);\n }, []);\n\n const attachLogs = useCallback(() => setLogsEnabled(true), []);\n const detachLogs = useCallback(() => setLogsEnabled(false), []);\n const clearLogs = useCallback(() => setLogs([]), []);\n const attachEvents = useCallback(() => setEventsEnabled(true), []);\n const detachEvents = useCallback(() => setEventsEnabled(false), []);\n const clearEvents = useCallback(() => {\n const device = config?.device;\n if (!device) return;\n setEventLogState((current) => clearIosEventLogState(current, device));\n }, [config?.device]);\n\n // ── Resolve the connection: discover the helper + log/device routes via /api. ──\n //\n // The Hub starts helpers explicitly (see `startIosHelper`) — it never boots a\n // sim just by connecting. So when the middleware is reachable but no helper is\n // attached yet (`/api` → null), we keep polling until the just-started helper\n // comes up, then resolve its streaming config. An unreachable middleware is\n // retried: `baseUrl` is never interpreted as a bare helper.\n useEffect(() => {\n if (!active || !baseUrl) {\n setResolvedConfig(null);\n setStatus('idle');\n return;\n }\n let cancelled = false;\n let pollTimer: ReturnType<typeof setTimeout> | null = null;\n setStatus('connecting');\n setError(null);\n\n // `baseUrl` may carry a path prefix (the plugin mount), so join onto it\n // rather than `new URL('/api', baseUrl)`, which would drop that prefix.\n const apiUrl = `${baseUrl.replace(/\\/$/, '')}/api${\n targetDevice ? `?device=${encodeURIComponent(targetDevice)}` : ''\n }`;\n\n const toMiddleware = (rawConfig: PreviewApi): ResolvedConfig => {\n const middlewareUrl = new URL(baseUrl, window.location.href);\n const c = proxyPreviewConfigForBrowser(rawConfig, middlewareUrl);\n const basePath = c.basePath === '/' ? '' : (c.basePath ?? '');\n const absoluteMiddlewareUrl = (path?: string): string | null =>\n path\n ? c.proxyHelpers\n ? middlewareEndpointForBrowser(path, middlewareUrl, basePath)\n : new URL(path, middlewareUrl).toString()\n : null;\n const appStateUrl = absoluteMiddlewareUrl(c.appStateEndpoint);\n return {\n url: c.url!,\n // An <img> and an EventSource cannot set a header.\n streamUrl: withSessionTokenQuery(c.streamUrl ?? `${c.url}/stream.mjpeg`, token),\n wsUrl: toQueryStyleHelperWsUrl(c.wsUrl ?? `${toWs(c.url!)}/ws`),\n device: c.device ?? null,\n execWsUrl: toWs(absoluteMiddlewareUrl(`${basePath}/exec-ws`)!),\n execToken: c.execToken ?? null,\n // These are subscription paths inside exec-ws, not browser URLs. The\n // server validates them against its internal middleware mount.\n logsPath: c.logsEndpoint ?? null,\n appStateUrl: appStateUrl && withSessionTokenQuery(appStateUrl, token),\n appIconUrl: absoluteMiddlewareUrl(c.appIconEndpoint),\n eventsPath: c.eventLogEventsEndpoint ?? null,\n metricsPath: c.metricsEndpoint ?? null,\n axUrl: absoluteMiddlewareUrl(c.axEndpoint),\n // A proxied helper URL uses the public middleware mount above rather\n // than an advertised internal host port that may be 0.\n streamSettingsUrl: c.streamSettingsEndpoint\n ? c.proxyHelpers\n ? `${c.url}/stream-settings`\n : absoluteMiddlewareUrl(c.streamSettingsEndpoint)\n : null,\n initialStreamSettings: c.streamSettings,\n gridApiUrl: absoluteMiddlewareUrl(c.gridApiEndpoint ?? `${basePath}/grid/api`),\n webRtcCodec: c.streamSettings?.transport === 'webrtc' ? c.streamSettings.codec : 'h264',\n ...(c.streamSettings?.transport === 'webrtc' && c.streamSettings.iceServers\n ? { webRtcIceServers: c.streamSettings.iceServers }\n : {}),\n };\n };\n\n // Ask the grid to attach a helper for this device at most once per effect\n // run (i.e. per device). Resets whenever `targetDevice`/`baseUrl` change.\n let startRequested = false;\n\n const resolve = async () => {\n if (cancelled) return;\n try {\n const res = await sessionFetch(apiUrl, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) {\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n return;\n }\n const c = (await res.json()) as PreviewApi | null;\n if (c && c.url && c.device) {\n if (!cancelled) {\n const resolved = toMiddleware(c);\n setWebRtcCodec(resolved.webRtcCodec);\n setResolvedConfig({\n key: connectionKey(baseUrl, targetDevice, token),\n config: resolved,\n });\n }\n return;\n }\n // Middleware reachable but no helper for this device yet. Ask the grid to\n // start one (once): a booted sim just gets a stream daemon; a shut-down\n // sim is booted. The middleware never does this on its own — only here,\n // because the user selected this device. Then poll until it attaches.\n if (targetDevice && !startRequested) {\n startRequested = true;\n void startIosHelper(targetDevice, baseUrl, sessionFetch).catch(() => {});\n }\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n } catch {\n if (!cancelled) pollTimer = setTimeout(resolve, RECONNECT_MS);\n }\n };\n void resolve();\n\n return () => {\n cancelled = true;\n if (pollTimer) clearTimeout(pollTimer);\n };\n }, [active, baseUrl, targetDevice, setWebRtcCodec, sessionFetch, token]);\n\n const fpsCounterRef = useRef({ frames: 0, startedAt: 0 });\n const onAvccFrame = useCallback((frameDelta = 1) => {\n const now = performance.now();\n const counter = fpsCounterRef.current;\n if (counter.startedAt === 0) counter.startedAt = now;\n counter.frames += frameDelta;\n if (now - counter.startedAt < 1_000) return;\n const next = Math.round((counter.frames * 1_000) / (now - counter.startedAt));\n counter.frames = 0;\n counter.startedAt = now;\n setFps((previous) => (previous === next ? previous : next));\n }, []);\n\n // ── WebRTC with serve-sim's codec and HTTP fallback policy. ──\n const {\n stream: webRtcStream,\n failure: webRtcFailure,\n error: webRtcError,\n markFrameDecoded: markWebRtcFrameDecoded,\n streamStats,\n setStreamStatsEnabled,\n } = useWebRtcStream({\n offerUrl: config ? `${config.url}/webrtc/offer` : '',\n closeUrl: config ? `${config.url}/webrtc/close` : '',\n closeBeaconUrl: config ? withSessionTokenQuery(`${config.url}/webrtc/close`, token) : '',\n statsUrl: config ? `${config.url}/webrtc/stats` : '',\n enabled: active && useWebRtc && !!config,\n codec: activeWebRtcCodec,\n iceServers: config?.webRtcIceServers,\n fetchImpl: sessionFetch,\n });\n const handledWebRtcFailureRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!useWebRtc || !webRtcFailure) return;\n if (handledWebRtcFailureRef.current === webRtcFailure.sessionId) return;\n handledWebRtcFailureRef.current = webRtcFailure.sessionId;\n const decision = webRtcFallbackDecision(webRtcCodec, activeWebRtcCodec, webRtcFailure);\n if (!decision) return;\n if (decision.type === 'switch-to-http') setWebRtcHttpFallback(true);\n else setActiveWebRtcCodec(decision.codec);\n }, [useWebRtc, webRtcFailure, webRtcCodec, activeWebRtcCodec]);\n\n useEffect(() => {\n if (!useWebRtc) return;\n if (webRtcError) {\n setStatus('error');\n setError(webRtcError);\n } else if (!webRtcStream) {\n setStatus('connecting');\n setError(null);\n }\n }, [useWebRtc, webRtcError, webRtcStream]);\n\n useEffect(() => {\n if (!useWebRtc) return;\n const video = videoRef.current;\n if (!video) return;\n let stopped = false;\n let firstFrame = true;\n let frameCallback = 0;\n let previousPresentedFrames: number | null = null;\n\n const markFrame = (presentedFrameDelta = 1) => {\n if (stopped) return;\n const { videoWidth: width, videoHeight: height } = video;\n if (width > 0 && height > 0 && !hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === width &&\n prev.height === height\n ? prev\n : { width, height },\n );\n }\n onAvccFrame(presentedFrameDelta);\n markWebRtcFrameDecoded(presentedFrameDelta);\n if (firstFrame) {\n firstFrame = false;\n setStatus('streaming');\n setError(null);\n }\n };\n const onVideoFrame: VideoFrameRequestCallback = (_now, metadata) => {\n const presentedFrameDelta = presentedVideoFrameDelta(\n previousPresentedFrames,\n metadata.presentedFrames,\n );\n if (Number.isSafeInteger(metadata.presentedFrames) && metadata.presentedFrames >= 0) {\n previousPresentedFrames = metadata.presentedFrames;\n }\n markFrame(presentedFrameDelta);\n frameCallback = video.requestVideoFrameCallback(onVideoFrame);\n };\n const onTimeUpdate = () => markFrame();\n const onLoadedData = () => markFrame(0);\n\n video.srcObject = webRtcStream;\n if (webRtcStream) {\n const supportsVideoFrameCallback = typeof video.requestVideoFrameCallback === 'function';\n if (supportsVideoFrameCallback) frameCallback = video.requestVideoFrameCallback(onVideoFrame);\n else video.addEventListener('timeupdate', onTimeUpdate);\n video.addEventListener('loadeddata', onLoadedData, { once: true });\n void video.play().catch(() => {});\n }\n\n return () => {\n stopped = true;\n video.removeEventListener('loadeddata', onLoadedData);\n video.removeEventListener('timeupdate', onTimeUpdate);\n if (frameCallback && typeof video.cancelVideoFrameCallback === 'function') {\n video.cancelVideoFrameCallback(frameCallback);\n }\n video.srcObject = null;\n };\n }, [useWebRtc, webRtcStream, markWebRtcFrameDecoded, onAvccFrame]);\n\n // ── H.264 AVCC (WebCodecs) with serve-sim's MJPEG fallback policy. ──\n useEffect(() => {\n dispatchAvccFallback('reset');\n setWebRtcHttpFallback(false);\n setFps(0);\n }, [streamMode, config?.url, config?.webRtcCodec]);\n\n useEffect(() => {\n if (!useAvcc || !config?.url) return;\n const timer = setTimeout(() => dispatchAvccFallback('timeout'), AVCC_FRAME_TIMEOUT_MS);\n return () => clearTimeout(timer);\n }, [useAvcc, config?.url]);\n\n useAvccStream({\n url: config?.url ?? '',\n enabled: active && useAvcc && !!config,\n canvasRef,\n fetchImpl: sessionFetch,\n onFirstFrame: () => {\n setStatus('streaming');\n setError(null);\n },\n onFrame: onAvccFrame,\n onDecodedFrame: () => dispatchAvccFallback('decoded-frame'),\n onResize: (width, height) => {\n if (!hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === width &&\n prev.height === height\n ? prev\n : { width, height },\n );\n }\n },\n onError: (message) => {\n setStatus('error');\n setError(message);\n },\n onDecoderError: () => dispatchAvccFallback('error'),\n });\n\n // ── MJPEG video (<img>) ──\n const streamUrl = useAvcc || useWebRtc ? null : (config?.streamUrl ?? null);\n useEffect(() => {\n if (!streamUrl) {\n streamUrlRef.current = null;\n return;\n }\n streamUrlRef.current = streamUrl;\n setStatus('connecting');\n setError(null);\n\n let cancelled = false;\n let settled = false;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n const img = imgRef.current;\n\n const markStreaming = () => {\n if (cancelled || settled) return;\n const el = imgRef.current;\n if (!el || el.naturalWidth === 0 || el.naturalHeight === 0) return;\n settled = true;\n setStatus('streaming');\n setError(null);\n if (!hasWsConfigRef.current) {\n setScreen((prev) =>\n prev &&\n prev.orientation === undefined &&\n prev.width === el.naturalWidth &&\n prev.height === el.naturalHeight\n ? prev\n : { width: el.naturalWidth, height: el.naturalHeight },\n );\n }\n };\n const onError = () => {\n if (cancelled) return;\n settled = false;\n setStatus('error');\n setError('Stream unavailable — retrying…');\n retryTimer = setTimeout(() => {\n if (!cancelled) applyStreamSrc();\n }, RECONNECT_MS);\n };\n img?.addEventListener('load', markStreaming);\n img?.addEventListener('error', onError);\n applyStreamSrc();\n const poll = setInterval(markStreaming, 400);\n\n return () => {\n cancelled = true;\n clearInterval(poll);\n if (retryTimer) clearTimeout(retryTimer);\n img?.removeEventListener('load', markStreaming);\n img?.removeEventListener('error', onError);\n const el = imgRef.current;\n if (el) el.removeAttribute('src');\n setScreen(null);\n setFps(0);\n };\n }, [streamUrl, applyStreamSrc]);\n\n // ── Helper control WebSocket (touch/buttons out, screen config in) ──\n const wsUrl = config?.wsUrl ?? null;\n useEffect(() => {\n setHardwareKeyboardConnectedState(null);\n if (!wsUrl) return;\n let cancelled = false;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n hasWsConfigRef.current = false;\n\n const connect = () => {\n if (cancelled) return;\n let ws: WebSocket;\n try {\n ws = new WebSocket(wsUrl, socketProtocols);\n } catch {\n return;\n }\n ws.binaryType = 'arraybuffer';\n wsRef.current = ws;\n ws.onopen = () => {\n // Deliver whatever the user did while the socket was down.\n pendingWsRef.current = flushWsMessageQueue(ws, pendingWsRef.current);\n // The Hub owns keyboard forwarding while this socket is active. Keep the\n // Simulator's separate host-keyboard connection off so iOS shows its\n // software keyboard while browser HID keys continue to type. serve-sim\n // reconnects it once the last input socket detaches.\n sendWs(WS_MSG_HARDWARE_KEYBOARD, { enabled: false });\n if (!cancelled) setHardwareKeyboardConnectedState(false);\n };\n ws.onmessage = (event) => {\n if (!(event.data instanceof ArrayBuffer)) return;\n const bytes = new Uint8Array(event.data);\n if (bytes.length < 1 || bytes[0] !== WS_TAG_SCREEN_CONFIG) return;\n try {\n const c = JSON.parse(decoder.decode(bytes.subarray(1))) as ScreenSize;\n if (c.width > 0 && c.height > 0) {\n hasWsConfigRef.current = true;\n setScreen((prev) =>\n prev &&\n prev.width === c.width &&\n prev.height === c.height &&\n prev.orientation === c.orientation\n ? prev\n : c,\n );\n }\n } catch {}\n };\n ws.onclose = () => {\n if (cancelled) return;\n wsRef.current = null;\n retryTimer = setTimeout(connect, RECONNECT_MS);\n };\n ws.onerror = () => {\n try {\n ws.close();\n } catch {}\n };\n };\n connect();\n\n return () => {\n cancelled = true;\n if (retryTimer) clearTimeout(retryTimer);\n try {\n wsRef.current?.close();\n } catch {}\n wsRef.current = null;\n // Queued input was for this device; don't replay it on the next one.\n pendingWsRef.current = [];\n setHardwareKeyboardConnectedState(null);\n };\n }, [wsUrl, sendWs, socketProtocols]);\n\n // ── Long-lived middleware SSE routes multiplexed over one authenticated\n // exec-ws, matching serve-sim's browser client. Keeping logs, events, and\n // metrics off separate HTTP streams avoids the per-origin connection cap. ──\n const execWsUrl = config?.execWsUrl ?? null;\n const execToken = config?.execToken ?? null;\n const logsPath = config?.logsPath ?? null;\n const eventsPath = config?.eventsPath ?? null;\n const metricsPath = config?.metricsPath ?? null;\n const deviceUdid = config?.device ?? null;\n const axUrl = config?.axUrl ?? null;\n\n const accessibilityLoader = useMemo<AccessibilityLoader | null>(\n () => (axUrl ? (signal) => loadIosAccessibility(axUrl, signal, sessionFetch) : null),\n [axUrl, sessionFetch],\n );\n const accessibilityState = useAccessibility(accessibilityLoader);\n\n // One-shot typed host actions (`location.*`, `app.*`) over the exec channel.\n const runAction = useMemo(\n () =>\n execWsUrl && execToken\n ? (action: string, params?: Parameters<typeof runHostAction>[3]) =>\n runHostAction(execWsUrl, execToken, action, params, socketProtocols)\n : null,\n [execWsUrl, execToken, socketProtocols],\n );\n\n const locationBackend = useMemo<DeviceLocationBackend | null>(() => {\n if (!runAction || !deviceUdid) return null;\n return {\n set: (fix) => setIosLocation(runAction, deviceUdid, fix),\n clear: () => clearIosLocation(runAction, deviceUdid),\n };\n }, [runAction, deviceUdid]);\n const {\n location,\n locationPending,\n locationError,\n setLocation,\n clearLocation,\n locationCapabilities,\n } = useDeviceLocation(locationBackend);\n\n // serve-sim exposes permissions over its CLI channel only, so the Hub has no\n // route to read them. The section stays hidden until serve-sim serves them.\n const appPermissions = useAppPermissions({ active, appId: null, backend: null });\n\n useEffect(() => {\n setEventLogState(createIosEventLogState());\n }, [eventsPath, deviceUdid]);\n\n useEffect(() => {\n activityLastSampleAtRef.current = 0;\n if (!metricsPath) {\n setActivity(null);\n return;\n }\n setActivity({ hostCores: null, samples: [], errored: false, stale: false });\n const watchdog = setInterval(() => {\n const lastSampleAt = activityLastSampleAtRef.current;\n if (lastSampleAt > 0 && Date.now() - lastSampleAt > ACTIVITY_STALE_MS) {\n setActivity((current) => (current && !current.stale ? { ...current, stale: true } : current));\n }\n }, 1000);\n return () => clearInterval(watchdog);\n }, [metricsPath]);\n\n useEffect(() => {\n if (!execWsUrl || !execToken) return;\n const subscriptions = new Map<number, 'logs' | 'events' | 'metrics'>();\n const paths = new Map<number, string>();\n if (logsEnabled && logsPath) {\n subscriptions.set(1, 'logs');\n paths.set(1, logsPath);\n }\n if (eventsEnabled && eventsPath && deviceUdid) {\n subscriptions.set(2, 'events');\n paths.set(2, eventsPath);\n }\n if (metricsPath) {\n subscriptions.set(3, 'metrics');\n paths.set(3, metricsPath);\n }\n if (subscriptions.size === 0) return;\n\n let cancelled = false;\n let ws: WebSocket | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n const buffers = new Map<number, string>();\n\n const markInterrupted = () => {\n if (metricsPath) {\n setActivity((current) => (current ? { ...current, errored: true } : current));\n }\n };\n\n const emit = (kind: 'logs' | 'events' | 'metrics', block: ParsedSseBlock) => {\n if (kind === 'logs') {\n let message = block.data;\n try {\n const parsed = JSON.parse(block.data) as { eventMessage?: string };\n if (typeof parsed.eventMessage === 'string') message = parsed.eventMessage;\n } catch {}\n if (message) {\n setLogs((previous) =>\n [\n ...previous,\n { id: `i${++logSeqRef.current}`, source: 'syslog', message },\n ].slice(-MAX_LOGS),\n );\n }\n return;\n }\n if (kind === 'events' && deviceUdid) {\n setEventLogState((current) =>\n mergeIosEventLogPayload(current, block.data, deviceUdid),\n );\n return;\n }\n if (kind !== 'metrics') return;\n try {\n const payload = JSON.parse(block.data) as unknown;\n if (block.event === 'meta') {\n const hostCores = parseActivityHostCores(payload);\n setActivity((current) =>\n current ? { ...current, hostCores, errored: false } : current,\n );\n return;\n }\n const sample = parseActivitySample(payload);\n if (!sample) return;\n activityLastSampleAtRef.current = Date.now();\n setActivity((current) =>\n appendActivitySample(\n current ?? { hostCores: null, samples: [], errored: false, stale: false },\n sample,\n ),\n );\n } catch {}\n };\n\n const connect = () => {\n if (cancelled) return;\n buffers.clear();\n try {\n ws = new WebSocket(execWsUrl, socketProtocols);\n } catch {\n markInterrupted();\n retryTimer = setTimeout(connect, RECONNECT_MS);\n return;\n }\n ws.onopen = () => ws?.send(JSON.stringify({ token: execToken }));\n ws.onmessage = (event) => {\n let msg: { ready?: boolean; sub?: number; data?: string; end?: boolean };\n try {\n msg = JSON.parse(String(event.data));\n } catch {\n return;\n }\n if (msg.ready) {\n for (const [sub, path] of paths) {\n ws?.send(JSON.stringify({ sub, path }));\n }\n return;\n }\n if (typeof msg.sub !== 'number' || !subscriptions.has(msg.sub)) return;\n if (msg.end) {\n markInterrupted();\n ws?.close();\n return;\n }\n if (typeof msg.data === 'string') {\n const sub = msg.sub;\n const kind = subscriptions.get(sub)!;\n buffers.set(\n sub,\n drainSseChunk(buffers.get(sub) ?? '', msg.data, (block) => emit(kind, block)),\n );\n }\n };\n ws.onclose = () => {\n if (!cancelled) {\n markInterrupted();\n retryTimer = setTimeout(connect, RECONNECT_MS);\n }\n };\n ws.onerror = () => {\n try {\n ws?.close();\n } catch {}\n };\n };\n connect();\n\n return () => {\n cancelled = true;\n if (retryTimer) clearTimeout(retryTimer);\n try {\n ws?.close();\n } catch {}\n };\n }, [\n logsEnabled,\n eventsEnabled,\n execWsUrl,\n execToken,\n logsPath,\n eventsPath,\n metricsPath,\n deviceUdid,\n socketProtocols,\n ]);\n\n // ── Simulator settings (best-effort) — one status request hydrates every\n // device-options control, including the appearance used by the toolbar. ──\n useEffect(() => {\n deviceSettingWriteTrackerRef.current.reset();\n setDeviceSettingsPending(new Set());\n if (!execWsUrl || !execToken || !deviceUdid) {\n setAppearanceState(null);\n setDeviceSettings(null);\n return;\n }\n let cancelled = false;\n hostUiRequest(execWsUrl, execToken, { device: deviceUdid }, socketProtocols)\n .then((res) => {\n if (cancelled) return;\n const next: DeviceSettings = {};\n for (const [key, value] of Object.entries(res.status ?? {})) {\n if (typeof value === 'string') next[key as DeviceSettingKey] = value;\n }\n setDeviceSettings(next);\n if (next.appearance === 'light' || next.appearance === 'dark') {\n setAppearanceState(next.appearance);\n }\n // The helper socket's open handler usually settles this first (it\n // disconnects the hardware keyboard); only fill in an unknown.\n const keyboardValue = res.status?.[UI_OPTION_HARDWARE_KEYBOARD];\n if (keyboardValue === 'on' || keyboardValue === 'off') {\n setHardwareKeyboardConnectedState((prev) => prev ?? keyboardValue === 'on');\n }\n })\n .catch(() => {\n /* unreachable / unsupported — leave unknown */\n });\n return () => {\n cancelled = true;\n };\n }, [execWsUrl, execToken, deviceUdid, socketProtocols]);\n\n // ── Runtime encoder settings (serve-sim helper GET/PATCH endpoint) ──\n const streamSettingsUrl = config?.streamSettingsUrl ?? null;\n const initialStreamSettings = config?.initialStreamSettings;\n const normalizedInitialStreamSettings = useMemo(\n () => (streamSettingsUrl ? normalizeDeviceStreamSettings(initialStreamSettings) : null),\n [initialStreamSettings, streamSettingsUrl],\n );\n const { streamSettings, streamSettingsPending, updateStreamSettings } = useStreamSettingsResource(\n {\n url: streamSettingsUrl,\n initialSettings: normalizedInitialStreamSettings,\n parse: parseIosStreamSettings,\n toPatch: iosStreamSettingsPatch,\n fetchImpl: sessionFetch,\n },\n );\n\n // ── Foreground app (middleware /appstate SSE) — the middleware bootstraps a\n // fresh subscriber with the current frontmost app, then pushes changes as\n // SpringBoard foregrounds apps. EventSource reconnects on its own. ──\n const appStateUrl = config?.appStateUrl ?? null;\n useEffect(() => {\n setForegroundApp(null);\n if (!appStateUrl) return;\n let source: EventSource | null = null;\n try {\n source = new EventSource(appStateUrl);\n } catch {\n return;\n }\n source.onmessage = (event) => {\n try {\n const data = JSON.parse(String(event.data)) as {\n bundleId?: string;\n pid?: number;\n isReactNative?: boolean;\n };\n if (data.bundleId) {\n // Merge repeat events for the same app so a relaunch (new pid)\n // doesn't wipe the bundle details filled in below.\n setForegroundApp((prev) =>\n prev && prev.id === data.bundleId\n ? prev.pid === data.pid && prev.isReactNative === data.isReactNative\n ? prev\n : { ...prev, pid: data.pid, isReactNative: data.isReactNative }\n : { id: data.bundleId!, pid: data.pid, isReactNative: data.isReactNative },\n );\n }\n } catch {}\n };\n return () => source?.close();\n }, [appStateUrl]);\n\n // ── Foreground app details (name, versions, icon) — introspected from the\n // app bundle on the host over exec-ws whenever the foreground bundle id\n // changes. Cached per udid:bundleId, so revisits apply instantly. A server\n // with the icon route serves the icon over plain HTTP instead, fetched\n // again on every foreground change. ──\n const foregroundAppId = foregroundApp?.id ?? null;\n const appIconUrl = config?.appIconUrl ?? null;\n useEffect(() => {\n if (!foregroundAppId || !runAction || !deviceUdid) return;\n let cancelled = false;\n getIosAppDetails(runAction, deviceUdid, foregroundAppId, { includeIcon: !appIconUrl })\n .then((details) => {\n if (cancelled || !details) return;\n setForegroundApp((prev) =>\n prev && prev.id === foregroundAppId ? { ...prev, ...details } : prev,\n );\n })\n .catch(() => {\n /* exec channel unavailable — the id/pid line still renders */\n });\n return () => {\n cancelled = true;\n };\n }, [foregroundAppId, runAction, deviceUdid, appIconUrl]);\n\n useEffect(() => {\n if (!foregroundAppId || !appIconUrl) return;\n const controller = new AbortController();\n fetchIosAppIcon(appIconUrl, foregroundAppId, {\n fetchImpl: sessionFetch,\n signal: controller.signal,\n })\n .then((iconDataUrl) => {\n if (controller.signal.aborted || !iconDataUrl) return;\n setForegroundApp((prev) =>\n prev && prev.id === foregroundAppId ? { ...prev, iconDataUrl } : prev,\n );\n })\n .catch(() => {\n /* route unavailable or app not installed — the placeholder still renders */\n });\n return () => controller.abort();\n }, [foregroundAppId, appIconUrl, sessionFetch]);\n\n // ── Running simulators (middleware /grid/api) ──\n const gridApiUrl = config?.gridApiUrl ?? null;\n useEffect(() => {\n if (!gridApiUrl) {\n setDevices(PLACEHOLDER_DEVICES);\n return;\n }\n let cancelled = false;\n sessionFetch(gridApiUrl, { signal: AbortSignal.timeout(3000) })\n .then((r) => r.json())\n .then((data: { devices?: Array<Record<string, unknown>> }) => {\n if (cancelled || !Array.isArray(data.devices) || data.devices.length === 0) return;\n setDevices(\n data.devices.map((d) => ({\n id: String(d.device ?? d.id ?? 'ios'),\n name: String(d.name ?? d.device ?? 'Simulator'),\n system: typeof d.runtime === 'string' ? d.runtime : undefined,\n platform: 'ios' as const,\n current: d.helper != null,\n })),\n );\n })\n .catch(() => {\n /* unreachable — keep the placeholder */\n });\n return () => {\n cancelled = true;\n };\n }, [gridApiUrl, sessionFetch]);\n\n const deviceSettingsAvailable = !!execWsUrl && !!execToken && !!deviceUdid;\n const activityAvailable = !!metricsPath;\n const eventsAvailable = !!eventsPath;\n const accessibilityAvailable = accessibilityLoader !== null;\n const streamSettingsAvailable = !!streamSettingsUrl;\n const capabilities = useMemo<DeviceCapabilities>(\n () => ({\n deviceSettings: deviceSettingsAvailable,\n activity: activityAvailable,\n events: eventsAvailable,\n camera: false,\n accessibility: accessibilityAvailable,\n streamSettings: streamSettingsAvailable ? IOS_STREAM_SETTING_CAPABILITIES : false,\n location: locationCapabilities,\n permissions: false,\n }),\n [\n deviceSettingsAvailable,\n activityAvailable,\n eventsAvailable,\n accessibilityAvailable,\n streamSettingsAvailable,\n locationCapabilities,\n ],\n );\n\n return {\n platform: 'ios',\n status,\n error,\n screen,\n fps,\n devices,\n logs,\n logsEnabled,\n attachLogs,\n detachLogs,\n clearLogs,\n events,\n eventsEnabled,\n attachEvents,\n detachEvents,\n clearEvents,\n activity,\n deviceSettings,\n deviceSettingsPending,\n setDeviceSetting,\n displayWidthDp: null,\n camera: null,\n cameraPending: NO_PENDING_CAMERA_WRITES,\n cameraError: null,\n setCameraImage: noop,\n clearCameraImage: noop,\n ...accessibilityState,\n location,\n locationPending,\n locationError,\n setLocation,\n clearLocation,\n ...appPermissions,\n streamCapabilities: IOS_STREAM_CAPABILITIES,\n screenRecording: null,\n streamSettings,\n streamSettingsPending,\n updateStreamSettings,\n streamSource: null,\n streamSourcePending: false,\n streamSourceError: null,\n setStreamSource: noop,\n setGrpcImageMode: noop,\n setGrpcEncoder: noop,\n setGrpcInputSource: noop,\n streamStats,\n setStreamStatsEnabled,\n webRtcCodec,\n setWebRtcCodec,\n capabilities,\n foregroundApp,\n videoKind: useWebRtc ? 'video' : useAvcc ? 'canvas' : 'img',\n attachVideo,\n sendTouch,\n sendMultiTouch,\n sendKey,\n sendKeyEvents,\n sendScroll,\n pressButton,\n reload,\n rotate,\n screenshot,\n appearance,\n setAppearance,\n hardwareKeyboardConnected,\n setHardwareKeyboardConnected,\n toggleSoftwareKeyboard,\n };\n}\n"],"names":["useCallback","useEffect","useLayoutEffect","useMemo","useReducer","useRef","useState","AVCC_FRAME_TIMEOUT_MS","avccFallbackReducer","initialAvccFallback","appendActivitySample","parseActivityHostCores","parseActivitySample","loadIosAccessibility","isAvccSupported","HID_EDGE_BOTTOM","homeIndicatorEdge","rawDeltaForDisplayDelta","rawEdgeForDisplayEdge","rawPointForDisplayPoint","streamGeometry","startIosHelper","clearIosEventLogState","createIosEventLogState","mergeIosEventLogPayload","hostUiRequest","runHostAction","fetchIosAppIcon","getIosAppDetails","clearIosLocation","setIosLocation","fetchScreenshot","hidUsageForCode","NO_PENDING_CAMERA_WRITES","mergeAuthoritativeDeviceSetting","KeyedWriteTracker","createPacedKeySender","middlewareEndpointForBrowser","proxyPreviewConfigForBrowser","sessionTokenFetch","sessionTokenProtocols","withSessionTokenQuery","drainSseChunk","normalizeDeviceStreamSettings","useAccessibility","useAppPermissions","useAvccStream","useDeviceLocation","useStreamSettingsResource","useWebRtcStream","presentedVideoFrameDelta","webRtcFallbackDecision","flushWsMessageQueue","sendOrQueueWsMessage","MAX_LOGS","RECONNECT_MS","ACTIVITY_STALE_MS","noop","WS_MSG_TOUCH","WS_MSG_BUTTON","WS_MSG_MULTI_TOUCH","WS_MSG_KEY","WS_MSG_ORIENTATION","WS_MSG_SCROLL","WS_MSG_SOFTWARE_KEYBOARD","WS_MSG_HARDWARE_KEYBOARD","WS_TAG_SCREEN_CONFIG","HID_USAGE_R","UI_OPTION_HARDWARE_KEYBOARD","PLACEHOLDER_DEVICES","id","name","platform","current","IOS_STREAM_CAPABILITIES","modeAvailability","mjpeg","h264","webrtc","httpCodecs","webRtcCodecs","IOS_STREAM_SETTING_CAPABILITIES","mjpegFps","mjpegQuality","maxDimension","h264Bitrate","h264Fps","ORIENTATION_CYCLE","BUTTON_NAME","home","appSwitcher","power","back","recents","hideKeyboard","decoder","TextDecoder","parseIosStreamSettings","value","fallback","iosStreamSettingsPatch","patch","Object","keys","length","toWs","url","replace","toQueryStyleHelperWsUrl","wsUrl","URL","match","pathname","Error","searchParams","has","set","decodeURIComponent","toString","connectionKey","baseUrl","device","token","JSON","stringify","useIosDeviceClient","options","enabled","targetDevice","streamMode","active","sessionFetch","socketProtocols","status","setStatus","error","setError","screen","setScreen","fps","setFps","logs","setLogs","logsEnabled","setLogsEnabled","eventLogState","setEventLogState","events","eventsEnabled","setEventsEnabled","activity","setActivity","devices","setDevices","resolvedConfig","setResolvedConfig","config","key","appearance","setAppearanceState","deviceSettings","setDeviceSettings","deviceSettingsPending","setDeviceSettingsPending","Set","hardwareKeyboardConnected","setHardwareKeyboardConnectedState","foregroundApp","setForegroundApp","wsRef","pendingWsRef","logSeqRef","imgRef","canvasRef","videoRef","streamUrlRef","avccFallback","dispatchAvccFallback","webRtcCodec","setWebRtcCodecState","activeWebRtcCodec","setActiveWebRtcCodec","webRtcHttpFallback","setWebRtcHttpFallback","deviceSettingWriteTrackerRef","deviceSettingConfigRef","activityLastSampleAtRef","useWebRtc","wantsAvcc","useAvcc","fellBack","edgeGestureRef","screenRef","hasWsConfigRef","applyStreamSrc","img","src","includes","Date","now","attachVideo","el","sendWs","tag","payload","sendTouch","sample","orientation","inputOrientation","displayEdge","phase","undefined","p","x","y","edge","type","sendMultiTouch","a","b","x1","y1","x2","y2","sendScroll","Number","isFinite","dx","dy","delta","anchor","sendKey","input","usage","code","keySender","event","dispose","sendKeyEvents","enqueue","setHardwareKeyboardConnected","connected","c","execWsUrl","execToken","previous","option","catch","toggleSoftwareKeyboard","pressButton","button","reload","Promise","r","setTimeout","rotate","next","indexOf","screenshot","udid","setDeviceSetting","tracker","request","start","pending","isCurrent","result","authoritative","nextKey","nextValue","entries","nextAppearance","finally","finish","setAppearance","mode","setWebRtcCodec","codec","attachLogs","detachLogs","clearLogs","attachEvents","detachEvents","clearEvents","cancelled","pollTimer","apiUrl","encodeURIComponent","toMiddleware","rawConfig","middlewareUrl","window","location","href","basePath","absoluteMiddlewareUrl","path","proxyHelpers","appStateUrl","appStateEndpoint","streamUrl","logsPath","logsEndpoint","appIconUrl","appIconEndpoint","eventsPath","eventLogEventsEndpoint","metricsPath","metricsEndpoint","axUrl","axEndpoint","streamSettingsUrl","streamSettingsEndpoint","initialStreamSettings","streamSettings","gridApiUrl","gridApiEndpoint","transport","iceServers","webRtcIceServers","startRequested","resolve","res","signal","AbortSignal","timeout","ok","json","resolved","clearTimeout","fpsCounterRef","frames","startedAt","onAvccFrame","frameDelta","performance","counter","Math","round","stream","webRtcStream","failure","webRtcFailure","webRtcError","markFrameDecoded","markWebRtcFrameDecoded","streamStats","setStreamStatsEnabled","offerUrl","closeUrl","closeBeaconUrl","statsUrl","fetchImpl","handledWebRtcFailureRef","sessionId","decision","video","stopped","firstFrame","frameCallback","previousPresentedFrames","markFrame","presentedFrameDelta","videoWidth","width","videoHeight","height","prev","onVideoFrame","_now","metadata","presentedFrames","isSafeInteger","requestVideoFrameCallback","onTimeUpdate","onLoadedData","srcObject","supportsVideoFrameCallback","addEventListener","once","play","removeEventListener","cancelVideoFrameCallback","timer","onFirstFrame","onFrame","onDecodedFrame","onResize","onError","message","onDecoderError","settled","retryTimer","markStreaming","naturalWidth","naturalHeight","poll","setInterval","clearInterval","removeAttribute","connect","ws","WebSocket","binaryType","onopen","onmessage","data","ArrayBuffer","bytes","Uint8Array","parse","decode","subarray","onclose","onerror","close","deviceUdid","accessibilityLoader","accessibilityState","runAction","action","params","locationBackend","fix","clear","locationPending","locationError","setLocation","clearLocation","locationCapabilities","appPermissions","appId","backend","hostCores","samples","errored","stale","watchdog","lastSampleAt","subscriptions","Map","paths","size","buffers","markInterrupted","emit","kind","block","parsed","eventMessage","source","slice","send","msg","String","ready","sub","end","get","reset","then","keyboardValue","normalizedInitialStreamSettings","streamSettingsPending","updateStreamSettings","initialSettings","toPatch","EventSource","bundleId","pid","isReactNative","foregroundAppId","includeIcon","details","controller","AbortController","iconDataUrl","aborted","abort","Array","isArray","map","d","system","runtime","helper","deviceSettingsAvailable","activityAvailable","eventsAvailable","accessibilityAvailable","streamSettingsAvailable","capabilities","camera","accessibility","permissions","displayWidthDp","cameraPending","cameraError","setCameraImage","clearCameraImage","streamCapabilities","screenRecording","streamSource","streamSourcePending","streamSourceError","setStreamSource","setGrpcImageMode","setGrpcEncoder","setGrpcInputSource","videoKind"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/hub-client",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Device-client React hooks and types for mirroring live iOS simulators and Android emulators in the browser through Expo Device Hub.",
5
5
  "keywords": [
6
6
  "expo",
@@ -4,10 +4,10 @@ import { type ForegroundApp } from './types';
4
4
 
5
5
  type FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;
6
6
 
7
- /** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. */
7
+ /** Mirrors `APP_ICON_MIME_TYPES` in serve-emu's API contracts. serve-sim's route answers the same shape. */
8
8
  const APP_ICON_MIME_TYPES = ['image/png', 'image/webp', 'image/jpeg', 'image/gif'];
9
9
 
10
- export function parseAndroidAppIcon(payload: unknown): string | null {
10
+ export function parseAppIconResponse(payload: unknown): string | null {
11
11
  const body = asRecord(payload);
12
12
  if (!body || body.ok !== true) throw new Error('app icon response is invalid');
13
13
  if (body.icon === null) return null;
@@ -31,7 +31,7 @@ export async function fetchAndroidAppIcon(
31
31
  url.searchParams.set('packageName', packageName);
32
32
  const res = await fetchImpl(url.toString(), { cache: 'no-store' });
33
33
  if (!res.ok) throw new Error(`app icon request failed with ${res.status}`);
34
- return parseAndroidAppIcon(await res.json());
34
+ return parseAppIconResponse(await res.json());
35
35
  }
36
36
 
37
37
  /**
@@ -1,3 +1,4 @@
1
+ import { parseAppIconResponse } from './android-app-icon';
1
2
  import { type HostActionResult, type RunHostAction } from './exec-ws';
2
3
  import { type ForegroundApp } from './types';
3
4
 
@@ -24,11 +25,14 @@ export type IosAppDetails = Pick<
24
25
  * bundle can't be located (e.g. the process is not a plain app — SpringBoard
25
26
  * has no user-visible container on some runtimes). Icons compiled solely into
26
27
  * Assets.car yield no `iconDataUrl`; callers should fall back to a placeholder.
28
+ * With `includeIcon: false` the icon is left to {@link fetchIosAppIcon} and the
29
+ * result carries no `iconDataUrl` key.
27
30
  */
28
31
  export async function fetchIosAppDetails(
29
32
  run: RunHostAction,
30
33
  udid: string,
31
34
  bundleId: string,
35
+ { includeIcon = true }: { includeIcon?: boolean } = {},
32
36
  ): Promise<IosAppDetails | null> {
33
37
  const ctn: HostActionResult = await run('app.container', { udid, bundleId });
34
38
  if (ctn.exitCode !== 0) return null;
@@ -53,7 +57,7 @@ export async function fetchIosAppDetails(
53
57
  else if (typeof info?.CFBundleIconFile === 'string') iconName = info.CFBundleIconFile;
54
58
 
55
59
  let iconDataUrl: string | undefined;
56
- if (iconName) {
60
+ if (includeIcon && iconName) {
57
61
  // Loose PNGs commonly sit next to Assets.car under a handful of names.
58
62
  const candidates = [
59
63
  `${iconName}@3x.png`,
@@ -79,10 +83,69 @@ export async function fetchIosAppDetails(
79
83
  build: info.CFBundleVersion,
80
84
  minOS: info.MinimumOSVersion,
81
85
  executable: info.CFBundleExecutable,
82
- iconDataUrl,
86
+ ...(includeIcon ? { iconDataUrl } : {}),
83
87
  };
84
88
  }
85
89
 
90
+ type FetchImpl = (input: string, init?: RequestInit) => Promise<Response>;
91
+
92
+ /** Waits before each retry of the icon request. */
93
+ const ICON_RETRY_DELAYS_MS = [1_000, 2_000, 4_000];
94
+
95
+ /**
96
+ * The app's icon from serve-sim's `/api/apps/icon` route (`appIconEndpoint` in
97
+ * `/api`): one plain GET, so it also reaches a tunneled server whose exec-ws
98
+ * socket the page cannot open. Null when the app has no loose icon PNG.
99
+ *
100
+ * Not cached: the route reads the installed bundle on each request, so a
101
+ * reinstalled build shows its new icon. A network error or a 5xx (the route
102
+ * answers 503 while simctl fails) is retried a few times. A 4xx is not,
103
+ * because the same request gets the same answer.
104
+ */
105
+ export async function fetchIosAppIcon(
106
+ appIconUrl: string,
107
+ bundleId: string,
108
+ {
109
+ fetchImpl = fetch,
110
+ signal,
111
+ retryDelaysMs = ICON_RETRY_DELAYS_MS,
112
+ }: { fetchImpl?: FetchImpl; signal?: AbortSignal; retryDelaysMs?: readonly number[] } = {},
113
+ ): Promise<string | null> {
114
+ const url = new URL(appIconUrl);
115
+ url.searchParams.set('bundleId', bundleId);
116
+ for (let attempt = 0; ; attempt++) {
117
+ const canRetry = attempt < retryDelaysMs.length;
118
+ let res: Response;
119
+ try {
120
+ res = await fetchImpl(url.toString(), { cache: 'no-store', signal });
121
+ } catch (err) {
122
+ if (signal?.aborted || !canRetry) throw err;
123
+ await delay(retryDelaysMs[attempt]!, signal);
124
+ continue;
125
+ }
126
+ if (res.ok) return parseAppIconResponse(await res.json());
127
+ if (res.status < 500 || !canRetry) {
128
+ throw new Error(`app icon request failed with ${res.status}`);
129
+ }
130
+ await delay(retryDelaysMs[attempt]!, signal);
131
+ }
132
+ }
133
+
134
+ function delay(ms: number, signal?: AbortSignal): Promise<void> {
135
+ return new Promise((resolve, reject) => {
136
+ if (signal?.aborted) return reject(signal.reason);
137
+ const onAbort = () => {
138
+ clearTimeout(timer);
139
+ reject(signal!.reason);
140
+ };
141
+ const timer = setTimeout(() => {
142
+ signal?.removeEventListener('abort', onAbort);
143
+ resolve();
144
+ }, ms);
145
+ signal?.addEventListener('abort', onAbort, { once: true });
146
+ });
147
+ }
148
+
86
149
  // Details (icon included) are immutable per installed build, so cache them
87
150
  // process-wide keyed by udid:bundleId — switching between apps re-applies
88
151
  // instantly. Rejections are evicted so a transient exec-ws failure retries on
@@ -93,11 +156,12 @@ export function getIosAppDetails(
93
156
  run: RunHostAction,
94
157
  udid: string,
95
158
  bundleId: string,
159
+ options: { includeIcon?: boolean } = {},
96
160
  ): Promise<IosAppDetails | null> {
97
- const key = `${udid}:${bundleId}`;
161
+ const key = `${udid}:${bundleId}:${options.includeIcon !== false}`;
98
162
  const cached = detailsCache.get(key);
99
163
  if (cached) return cached;
100
- const pending = fetchIosAppDetails(run, udid, bundleId).catch((err) => {
164
+ const pending = fetchIosAppDetails(run, udid, bundleId, options).catch((err) => {
101
165
  detailsCache.delete(key);
102
166
  throw err;
103
167
  });
@@ -50,7 +50,7 @@ import {
50
50
  mergeIosEventLogPayload,
51
51
  } from './ios-events';
52
52
  import { hostUiRequest, runHostAction } from './exec-ws';
53
- import { getIosAppDetails } from './ios-app-details';
53
+ import { fetchIosAppIcon, getIosAppDetails } from './ios-app-details';
54
54
  import { clearIosLocation, setIosLocation } from './ios-location';
55
55
  import { fetchScreenshot } from './screenshot';
56
56
  import { hidUsageForCode } from './keyboard';
@@ -215,6 +215,8 @@ interface ResolvedConfig {
215
215
  logsPath: string | null;
216
216
  /** Absolute URL of the foreground-app SSE stream. */
217
217
  appStateUrl: string | null;
218
+ /** Absolute URL of the app icon route, when the server has one. */
219
+ appIconUrl: string | null;
218
220
  /** Relative SSE path for normalized serve-sim events. */
219
221
  eventsPath: string | null;
220
222
  /** Relative SSE path for foreground app activity. */
@@ -239,6 +241,7 @@ interface PreviewApi {
239
241
  execToken?: string;
240
242
  logsEndpoint?: string;
241
243
  appStateEndpoint?: string;
244
+ appIconEndpoint?: string;
242
245
  eventLogEventsEndpoint?: string;
243
246
  metricsEndpoint?: string;
244
247
  axEndpoint?: string;
@@ -630,6 +633,7 @@ export function useIosDeviceClient(options: DeviceConnectionOptions): DeviceClie
630
633
  // server validates them against its internal middleware mount.
631
634
  logsPath: c.logsEndpoint ?? null,
632
635
  appStateUrl: appStateUrl && withSessionTokenQuery(appStateUrl, token),
636
+ appIconUrl: absoluteMiddlewareUrl(c.appIconEndpoint),
633
637
  eventsPath: c.eventLogEventsEndpoint ?? null,
634
638
  metricsPath: c.metricsEndpoint ?? null,
635
639
  axUrl: absoluteMiddlewareUrl(c.axEndpoint),
@@ -1295,12 +1299,15 @@ export function useIosDeviceClient(options: DeviceConnectionOptions): DeviceClie
1295
1299
 
1296
1300
  // ── Foreground app details (name, versions, icon) — introspected from the
1297
1301
  // app bundle on the host over exec-ws whenever the foreground bundle id
1298
- // changes. Cached per udid:bundleId, so revisits apply instantly. ──
1302
+ // changes. Cached per udid:bundleId, so revisits apply instantly. A server
1303
+ // with the icon route serves the icon over plain HTTP instead, fetched
1304
+ // again on every foreground change. ──
1299
1305
  const foregroundAppId = foregroundApp?.id ?? null;
1306
+ const appIconUrl = config?.appIconUrl ?? null;
1300
1307
  useEffect(() => {
1301
1308
  if (!foregroundAppId || !runAction || !deviceUdid) return;
1302
1309
  let cancelled = false;
1303
- getIosAppDetails(runAction, deviceUdid, foregroundAppId)
1310
+ getIosAppDetails(runAction, deviceUdid, foregroundAppId, { includeIcon: !appIconUrl })
1304
1311
  .then((details) => {
1305
1312
  if (cancelled || !details) return;
1306
1313
  setForegroundApp((prev) =>
@@ -1313,7 +1320,26 @@ export function useIosDeviceClient(options: DeviceConnectionOptions): DeviceClie
1313
1320
  return () => {
1314
1321
  cancelled = true;
1315
1322
  };
1316
- }, [foregroundAppId, runAction, deviceUdid]);
1323
+ }, [foregroundAppId, runAction, deviceUdid, appIconUrl]);
1324
+
1325
+ useEffect(() => {
1326
+ if (!foregroundAppId || !appIconUrl) return;
1327
+ const controller = new AbortController();
1328
+ fetchIosAppIcon(appIconUrl, foregroundAppId, {
1329
+ fetchImpl: sessionFetch,
1330
+ signal: controller.signal,
1331
+ })
1332
+ .then((iconDataUrl) => {
1333
+ if (controller.signal.aborted || !iconDataUrl) return;
1334
+ setForegroundApp((prev) =>
1335
+ prev && prev.id === foregroundAppId ? { ...prev, iconDataUrl } : prev,
1336
+ );
1337
+ })
1338
+ .catch(() => {
1339
+ /* route unavailable or app not installed — the placeholder still renders */
1340
+ });
1341
+ return () => controller.abort();
1342
+ }, [foregroundAppId, appIconUrl, sessionFetch]);
1317
1343
 
1318
1344
  // ── Running simulators (middleware /grid/api) ──
1319
1345
  const gridApiUrl = config?.gridApiUrl ?? null;