@limrun/ui 0.15.0 → 0.16.0
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 +27 -0
- package/dist/components/remote-control-framed.d.ts +2 -0
- package/dist/components/remote-control.d.ts +28 -71
- package/dist/core/ax-fetcher.d.ts +5 -0
- package/dist/core/camera.d.ts +58 -0
- package/dist/frame-assets.d.ts +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +25 -2447
- package/dist/lite.cjs +1 -0
- package/dist/lite.d.ts +6 -0
- package/dist/lite.js +13 -0
- package/dist/remote-control-BRgbD7yo.mjs +2516 -0
- package/dist/remote-control-VISrP1zI.js +1 -0
- package/package.json +11 -4
- package/src/components/remote-control-framed.tsx +14 -0
- package/src/components/remote-control.tsx +369 -435
- package/src/core/ax-fetcher.ts +15 -5
- package/src/core/camera.test.ts +155 -0
- package/src/core/camera.ts +133 -0
- package/src/demo.tsx +2 -1
- package/src/frame-assets.ts +22 -0
- package/src/index.ts +13 -3
- package/src/lite.test.ts +30 -0
- package/src/lite.ts +30 -0
- package/vite.config.ts +5 -0
package/README.md
CHANGED
|
@@ -6,6 +6,33 @@ types and helpers for building inspect, search, and agent interfaces.
|
|
|
6
6
|
|
|
7
7
|
See [examples](../../examples/) to see how it can be used.
|
|
8
8
|
|
|
9
|
+
## Frameless embeds
|
|
10
|
+
|
|
11
|
+
`@limrun/ui` bundles Limrun's device frames and boot logos as inline images, about
|
|
12
|
+
600 KB. Embeds that render the bare screen can import `@limrun/ui/lite` instead: the same
|
|
13
|
+
`RemoteControl` API without any artwork, so `showFrame` has nothing to draw and the boot
|
|
14
|
+
logo is omitted. Pass your own images through the `assets` prop if you want frames back.
|
|
15
|
+
|
|
16
|
+
`onConnectionStateChange` reports `connecting`, `connected`, `reconnecting`, `failed`, and
|
|
17
|
+
`terminated` so a host can show status text without tracking the retry logic.
|
|
18
|
+
|
|
19
|
+
## Simulated cameras
|
|
20
|
+
|
|
21
|
+
`RemoteControl` responds automatically when an app in the instance opens,
|
|
22
|
+
closes, or switches a virtual camera. Camera facing is a preference: the
|
|
23
|
+
component requests the browser's `user` or `environment` camera with an
|
|
24
|
+
`ideal` constraint and falls back to an available input. Repeated requests for
|
|
25
|
+
the same facing reuse the live track. Facing changes restart capture so mobile
|
|
26
|
+
browsers can release one camera before opening the other.
|
|
27
|
+
|
|
28
|
+
WebRTC and the browser choose the capture resolution. The simulated camera's
|
|
29
|
+
host output follows the decoded frame dimensions, including width/height swaps
|
|
30
|
+
when captured video rotates. `onCameraDemandChange` receives fresh
|
|
31
|
+
`MediaStreamTrack.getSettings()` status metadata after track changes.
|
|
32
|
+
|
|
33
|
+
Camera API types (`CameraRequest`, `CameraResult`, `CameraFacingMode`,
|
|
34
|
+
`CameraCaptureInfo`, and `CameraStreamStats`) are exported from `@limrun/ui`.
|
|
35
|
+
|
|
9
36
|
Related browser workflow packages are published separately:
|
|
10
37
|
|
|
11
38
|
- `@limrun/apple-auth` handles Apple ID login, signing credentials, and App
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { AxStatus } from '../core/ax-fetcher';
|
|
2
|
+
import { AxStatus, AndroidElementTreeOptions } from '../core/ax-fetcher';
|
|
3
3
|
import { AxElement, AxSnapshot } from '../core/ax-tree';
|
|
4
|
+
import { CameraCaptureInfo } from '../core/camera';
|
|
5
|
+
export type { CameraCaptureInfo, CameraFacingMode, CameraRequest, CameraResult } from '../core/camera';
|
|
4
6
|
declare global {
|
|
5
7
|
interface Window {
|
|
6
8
|
debugRemoteControl?: boolean;
|
|
7
9
|
}
|
|
8
10
|
}
|
|
9
|
-
interface RemoteControlProps {
|
|
11
|
+
export interface RemoteControlProps {
|
|
10
12
|
url: string;
|
|
11
13
|
token: string;
|
|
12
14
|
className?: string;
|
|
@@ -26,6 +28,15 @@ interface RemoteControlProps {
|
|
|
26
28
|
* etc.). It is not called for ordinary network drops.
|
|
27
29
|
*/
|
|
28
30
|
onTerminated?: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Device frame and boot-logo images. The default export bundles Limrun's
|
|
33
|
+
* frames; `@limrun/ui/lite` omits them so a frameless embed stays small.
|
|
34
|
+
*/
|
|
35
|
+
assets?: RemoteControlAssets;
|
|
36
|
+
/**
|
|
37
|
+
* Coarse connection lifecycle, for status UI outside the component.
|
|
38
|
+
*/
|
|
39
|
+
onConnectionStateChange?: (state: RemoteControlConnectionState) => void;
|
|
29
40
|
/**
|
|
30
41
|
* Enable the inspect overlay. When set, the component starts polling the
|
|
31
42
|
* accessibility tree and draws boxes over each element on top of the
|
|
@@ -105,6 +116,7 @@ interface RemoteControlProps {
|
|
|
105
116
|
* @default 2000
|
|
106
117
|
*/
|
|
107
118
|
axMaxBackoffMs?: number;
|
|
119
|
+
androidElementTreeOptions?: AndroidElementTreeOptions;
|
|
108
120
|
/**
|
|
109
121
|
* Fires whenever the iOS simulator's camera demand state changes —
|
|
110
122
|
* i.e. an app inside the sim called
|
|
@@ -114,12 +126,10 @@ interface RemoteControlProps {
|
|
|
114
126
|
* UI can render a status indicator ("simulator is using your
|
|
115
127
|
* camera", etc.).
|
|
116
128
|
*
|
|
117
|
-
* `active` reflects whether the sim is currently asking for
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* (in which case the limulator side switches to a black-frame
|
|
122
|
-
* fallback so the app keeps ticking).
|
|
129
|
+
* `active` reflects whether the sim is currently asking for frames.
|
|
130
|
+
* `granted` is set on capture acknowledgements: `true` after a new
|
|
131
|
+
* track, reused track, or constraint update; `false` when capture
|
|
132
|
+
* fails. The host switches to black frames after a failure.
|
|
123
133
|
*
|
|
124
134
|
* `camera` (optional, only meaningful when `granted === true`)
|
|
125
135
|
* carries what `MediaStreamTrack.getSettings()` reported for the
|
|
@@ -149,40 +159,6 @@ interface RemoteControlProps {
|
|
|
149
159
|
* itself — this is the canonical place.
|
|
150
160
|
*/
|
|
151
161
|
onCameraStats?: (stats: CameraStreamStats | null) => void;
|
|
152
|
-
/**
|
|
153
|
-
* Optional resolution cap for outbound camera capture.
|
|
154
|
-
*
|
|
155
|
-
* - `'auto'` (default): no extra constraint. The browser captures
|
|
156
|
-
* at its webcam's native max; WebRTC's quality scaler may step
|
|
157
|
-
* down resolution on the encoder side under bandwidth pressure
|
|
158
|
-
* while still feeding the simulator at the pool's native size.
|
|
159
|
-
* - `'1080p'` / `'720p'` / `'480p'`: hard cap applied via
|
|
160
|
-
* `getUserMedia` constraints (for new captures) and
|
|
161
|
-
* `track.applyConstraints` (for the currently-active track),
|
|
162
|
-
* matching the way Meet/Zoom expose a "Send resolution" picker.
|
|
163
|
-
*
|
|
164
|
-
* Bumping or lowering the cap mid-stream is supported; the
|
|
165
|
-
* change takes effect within a frame or two as the webcam
|
|
166
|
-
* re-negotiates.
|
|
167
|
-
*/
|
|
168
|
-
cameraResolutionCap?: CameraResolutionCap;
|
|
169
|
-
/**
|
|
170
|
-
* Aspect ratio the simulator's virtual camera should report to apps.
|
|
171
|
-
* Picking a value here triggers a `cameraAspect` WS message to the
|
|
172
|
-
* host, which rebuilds its IOSurface ring at the matching dimensions
|
|
173
|
-
* (16:9 → 1920×1080, 4:3 → 1440×1080, 1:1 → 1080×1080, 9:16 →
|
|
174
|
-
* 1080×1920) and signals the in-sim dylib to re-handshake. iOS apps
|
|
175
|
-
* see CMSampleBuffers at the new dimensions within a frame or two.
|
|
176
|
-
*
|
|
177
|
-
* The browser still captures whatever the webcam offers; the host
|
|
178
|
-
* aspect-fills (cover, center-crop) into the new pool. Switching
|
|
179
|
-
* aspect at runtime is intentionally cheap so users can A/B preview
|
|
180
|
-
* styles without restarting the simulator.
|
|
181
|
-
*
|
|
182
|
-
* `undefined` leaves the pool untouched (the host's boot default —
|
|
183
|
-
* 16:9 / 1920×1080 — applies).
|
|
184
|
-
*/
|
|
185
|
-
cameraAspect?: CameraAspect;
|
|
186
162
|
/**
|
|
187
163
|
* When true, capture the user's microphone via
|
|
188
164
|
* `getUserMedia({ audio: true })` and stream it live into the
|
|
@@ -218,34 +194,6 @@ export interface MicrophoneState {
|
|
|
218
194
|
/** Device label of the captured microphone, when available. */
|
|
219
195
|
label?: string;
|
|
220
196
|
}
|
|
221
|
-
/**
|
|
222
|
-
* Resolution caps a host app can request on the outbound camera.
|
|
223
|
-
* `'auto'` is "let the browser decide" (no constraints beyond the
|
|
224
|
-
* 30 fps ceiling); the other options clamp width/height to the
|
|
225
|
-
* named target. Aspect ratio is preserved.
|
|
226
|
-
*/
|
|
227
|
-
export type CameraResolutionCap = 'auto' | '1080p' | '720p' | '480p';
|
|
228
|
-
/**
|
|
229
|
-
* Aspect ratios exposed to the operator for the simulated camera.
|
|
230
|
-
* The host maps each label to concrete IOSurface dimensions; values
|
|
231
|
-
* the host doesn't recognise are silently ignored.
|
|
232
|
-
*/
|
|
233
|
-
export type CameraAspect = '16:9' | '4:3' | '1:1' | '9:16';
|
|
234
|
-
/**
|
|
235
|
-
* Snapshot of the browser's webcam capture, mirrored from
|
|
236
|
-
* `MediaStreamTrack.getSettings()`. Forwarded to the host alongside
|
|
237
|
-
* `cameraResult` and exposed to the host app via
|
|
238
|
-
* `onCameraDemandChange` so it can render a status indicator without
|
|
239
|
-
* having to call `getStats()` itself.
|
|
240
|
-
*/
|
|
241
|
-
export interface CameraCaptureInfo {
|
|
242
|
-
width?: number;
|
|
243
|
-
height?: number;
|
|
244
|
-
frameRate?: number;
|
|
245
|
-
deviceId?: string;
|
|
246
|
-
label?: string;
|
|
247
|
-
facingMode?: string;
|
|
248
|
-
}
|
|
249
197
|
/**
|
|
250
198
|
* Live outbound-camera quality snapshot. Derived from
|
|
251
199
|
* `RTCPeerConnection.getStats()` and rate-derived deltas, sampled
|
|
@@ -308,6 +256,15 @@ export interface RemoteControlHandle {
|
|
|
308
256
|
setInspectSelection: (element: AxElement | null) => void;
|
|
309
257
|
getAxStatus: () => AxStatus;
|
|
310
258
|
}
|
|
259
|
+
type DevicePlatform = 'ios' | 'android';
|
|
311
260
|
export declare const withAuthenticationToken: (url: string, token: string) => string;
|
|
261
|
+
export type RemoteControlConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'failed' | 'terminated';
|
|
262
|
+
export type RemoteControlPlatformAssets = {
|
|
263
|
+
frame?: string;
|
|
264
|
+
frameLandscape?: string;
|
|
265
|
+
tabletFrame?: string;
|
|
266
|
+
tabletFrameLandscape?: string;
|
|
267
|
+
loadingLogo?: string;
|
|
268
|
+
};
|
|
269
|
+
export type RemoteControlAssets = Partial<Record<DevicePlatform, RemoteControlPlatformAssets>>;
|
|
312
270
|
export declare const RemoteControl: React.ForwardRefExoticComponent<RemoteControlProps & React.RefAttributes<RemoteControlHandle>>;
|
|
313
|
-
export {};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { AxPlatform, AxSnapshot } from './ax-tree';
|
|
2
2
|
export type AxStatus = 'idle' | 'starting' | 'ready' | 'unavailable' | 'error';
|
|
3
3
|
export type AxFetcherSendFn = (payload: Record<string, unknown>) => boolean;
|
|
4
|
+
export interface AndroidElementTreeOptions {
|
|
5
|
+
waitForIdleTimeoutMs?: number;
|
|
6
|
+
}
|
|
4
7
|
export interface AxFetcherOptions {
|
|
5
8
|
platform: AxPlatform;
|
|
6
9
|
send: AxFetcherSendFn;
|
|
7
10
|
onSnapshot: (snapshot: AxSnapshot | null) => void;
|
|
11
|
+
androidElementTreeOptions?: AndroidElementTreeOptions;
|
|
8
12
|
onStatusChange?: (status: AxStatus, error?: string) => void;
|
|
9
13
|
baseIntervalMs?: number;
|
|
10
14
|
maxBackoffMs?: number;
|
|
@@ -16,6 +20,7 @@ export declare class AxFetcher {
|
|
|
16
20
|
private readonly onStatusChange?;
|
|
17
21
|
private readonly baseIntervalMs;
|
|
18
22
|
private readonly maxBackoffMs;
|
|
23
|
+
private readonly androidElementTreeOptions?;
|
|
19
24
|
private readonly pending;
|
|
20
25
|
private running;
|
|
21
26
|
private timer;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type CameraFacingMode = 'user' | 'environment';
|
|
2
|
+
/** Camera selection requested by the instance host. */
|
|
3
|
+
export interface CameraRequest {
|
|
4
|
+
active: boolean;
|
|
5
|
+
facingMode?: CameraFacingMode;
|
|
6
|
+
}
|
|
7
|
+
/** Browser capture metadata returned to the instance host. */
|
|
8
|
+
export interface CameraCaptureInfo {
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
frameRate?: number;
|
|
12
|
+
deviceId?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
facingMode?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Acknowledgement sent after camera capture changes or is reused. */
|
|
17
|
+
export interface CameraResult {
|
|
18
|
+
type: 'cameraResult';
|
|
19
|
+
granted: boolean;
|
|
20
|
+
camera?: CameraCaptureInfo;
|
|
21
|
+
}
|
|
22
|
+
export interface CameraOperationContext {
|
|
23
|
+
generation: number;
|
|
24
|
+
isCurrent: () => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Serializes camera mutations while invalidating queued or suspended work.
|
|
28
|
+
*
|
|
29
|
+
* `enqueue` increments the generation before returning, so an operation
|
|
30
|
+
* waiting on browser permission becomes stale as soon as a newer request
|
|
31
|
+
* arrives, even though the newer operation has not started yet.
|
|
32
|
+
*/
|
|
33
|
+
export declare class CameraOperationCoordinator {
|
|
34
|
+
private generation;
|
|
35
|
+
private tail;
|
|
36
|
+
get currentGeneration(): number;
|
|
37
|
+
enqueue(operation: (context: CameraOperationContext) => Promise<void>): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Queues related work without superseding an already queued request.
|
|
40
|
+
*
|
|
41
|
+
* The operation retains the generation current at enqueue time and becomes
|
|
42
|
+
* stale if a later camera request increments it before this work starts.
|
|
43
|
+
*/
|
|
44
|
+
enqueueCurrent(operation: (context: CameraOperationContext) => Promise<void>): Promise<void>;
|
|
45
|
+
private append;
|
|
46
|
+
invalidate(): void;
|
|
47
|
+
}
|
|
48
|
+
export declare function parseCameraRequest(message: unknown): CameraRequest | null;
|
|
49
|
+
/**
|
|
50
|
+
* Builds best-effort capture constraints.
|
|
51
|
+
*
|
|
52
|
+
* Facing is a preference so browsers can fall back when a phone-facing label
|
|
53
|
+
* or exact mode is unavailable. The browser chooses capture dimensions.
|
|
54
|
+
*/
|
|
55
|
+
export declare function cameraCaptureConstraints(request: CameraRequest): MediaTrackConstraints;
|
|
56
|
+
export declare function shouldReacquireCamera(current: Pick<CameraRequest, 'facingMode'> | undefined, requested: Pick<CameraRequest, 'facingMode'>): boolean;
|
|
57
|
+
export declare function cameraCaptureInfoFromTrack(track: MediaStreamTrack): CameraCaptureInfo;
|
|
58
|
+
export declare function createGrantedCameraResult(track: MediaStreamTrack): CameraResult;
|