@limrun/api 0.46.7 → 0.46.9
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/CHANGELOG.md +8 -0
- package/exec-client.d.mts +39 -1
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +39 -1
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js +215 -69
- package/exec-client.js.map +1 -1
- package/exec-client.mjs +212 -68
- package/exec-client.mjs.map +1 -1
- package/ios-client.d.mts +34 -7
- package/ios-client.d.mts.map +1 -1
- package/ios-client.d.ts +34 -7
- package/ios-client.d.ts.map +1 -1
- package/ios-client.js +15 -4
- package/ios-client.js.map +1 -1
- package/ios-client.mjs +14 -3
- package/ios-client.mjs.map +1 -1
- package/package.json +1 -1
- package/src/exec-client.ts +224 -65
- package/src/ios-client.ts +62 -11
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/package.json
CHANGED
package/src/exec-client.ts
CHANGED
|
@@ -7,6 +7,34 @@
|
|
|
7
7
|
|
|
8
8
|
import { createEventSource, type EventSourceClient, type EventSourceMessage } from 'eventsource-client';
|
|
9
9
|
import { nodeProxyTransport } from './internal/proxy-transport';
|
|
10
|
+
import { sseFetch } from './internal/sse-fetch';
|
|
11
|
+
import type { Fetch } from './internal/builtin-types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Give-up policy for the exec event stream. The EventSource reconnects
|
|
15
|
+
* forever on its own, so a dead exec (deleted instance, expired record)
|
|
16
|
+
* would otherwise retry until the one-hour completion timeout. Proof of
|
|
17
|
+
* life resets the clock: a delivered event, or a clean connection that
|
|
18
|
+
* stays open healthyConnectionMs (long compiles stream nothing for
|
|
19
|
+
* minutes while intermediaries kill idle connections). The window is
|
|
20
|
+
* wide enough to ride out real transient outages (an ingress redeploy, a
|
|
21
|
+
* VPN reconnect); tightening it trades outage tolerance for faster
|
|
22
|
+
* dead-exec detection. It is measured in time, not attempts: the server
|
|
23
|
+
* steers the retry delay through SSE `retry:` frames, so an attempt
|
|
24
|
+
* count would put the give-up horizon under remote control.
|
|
25
|
+
*
|
|
26
|
+
* @internal Mutable for tests only.
|
|
27
|
+
*/
|
|
28
|
+
export const sseStreamPolicy = {
|
|
29
|
+
healthyConnectionMs: 15_000,
|
|
30
|
+
giveUpAfterMs: 300_000,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** The event stream kept failing past the give-up window; remote state unknown. */
|
|
34
|
+
export class ExecStreamLostError extends Error {}
|
|
35
|
+
|
|
36
|
+
/** The server closed the event stream for good (HTTP 204). */
|
|
37
|
+
export class ExecStreamClosedError extends Error {}
|
|
10
38
|
|
|
11
39
|
// =============================================================================
|
|
12
40
|
// Types
|
|
@@ -211,6 +239,14 @@ export type ExecResult = {
|
|
|
211
239
|
* running and may yet succeed.
|
|
212
240
|
*/
|
|
213
241
|
timedOut?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Why the client gave up, present exactly when timedOut is true.
|
|
244
|
+
* 'timeout' is the completion budget expiring on a live stream;
|
|
245
|
+
* 'stream-lost' means the event stream kept failing (the execution may
|
|
246
|
+
* no longer exist); 'stream-closed' means the server ended the stream
|
|
247
|
+
* for good.
|
|
248
|
+
*/
|
|
249
|
+
incomplete?: { reason: 'timeout' | 'stream-lost' | 'stream-closed'; message: string };
|
|
214
250
|
};
|
|
215
251
|
|
|
216
252
|
export type AppStoreEvent = {
|
|
@@ -327,6 +363,10 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
327
363
|
private abortController = new AbortController();
|
|
328
364
|
private sseConnection: EventSourceClient | null = null;
|
|
329
365
|
private killed = false;
|
|
366
|
+
// Start of the stream's current unbroken failure streak, 0 while
|
|
367
|
+
// healthy. run() reads it to classify a completion timeout that
|
|
368
|
+
// expired while the stream was already dead.
|
|
369
|
+
private streamDeadSince = 0;
|
|
330
370
|
private detached = false;
|
|
331
371
|
private appStoreEvent: AppStoreEvent | null = null;
|
|
332
372
|
private playstoreEvent: PlaystoreEvent | null = null;
|
|
@@ -501,22 +541,41 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
501
541
|
}
|
|
502
542
|
let exitCode: number;
|
|
503
543
|
let timedOut = false;
|
|
544
|
+
let incomplete: ExecResult['incomplete'];
|
|
504
545
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
505
546
|
try {
|
|
506
547
|
exitCode = await Promise.race([
|
|
507
548
|
this.connectSSE(eventsUrl),
|
|
508
549
|
new Promise<never>((_, reject) => {
|
|
509
|
-
timeoutId = setTimeout(() => reject(new Error('SSE timeout')), timeoutMs);
|
|
550
|
+
timeoutId = setTimeout(() => reject(new Error('SSE completion timeout')), timeoutMs);
|
|
510
551
|
}),
|
|
511
552
|
]);
|
|
512
|
-
} catch {
|
|
553
|
+
} catch (err) {
|
|
513
554
|
if (this.killed) {
|
|
514
555
|
log('debug', 'Execution killed');
|
|
515
556
|
exitCode = -1;
|
|
516
557
|
} else {
|
|
517
|
-
// The client stopped waiting; the
|
|
518
|
-
//
|
|
519
|
-
|
|
558
|
+
// The client stopped waiting; the fabricated exit code must not
|
|
559
|
+
// read as a build failure. The structured reason lets callers give
|
|
560
|
+
// the right advice: a dead stream means the execution may be gone,
|
|
561
|
+
// while a genuine timeout means it may still be running. A budget
|
|
562
|
+
// that expires mid-failure-streak (short run timeouts undercut the
|
|
563
|
+
// stream's own give-up window) counts as a stream problem too.
|
|
564
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
565
|
+
const streakMs = this.streamDeadSince > 0 ? Date.now() - this.streamDeadSince : 0;
|
|
566
|
+
let reason: NonNullable<ExecResult['incomplete']>['reason'] =
|
|
567
|
+
err instanceof ExecStreamClosedError ? 'stream-closed'
|
|
568
|
+
: err instanceof ExecStreamLostError ? 'stream-lost'
|
|
569
|
+
: 'timeout';
|
|
570
|
+
if (reason === 'timeout' && streakMs > 0) {
|
|
571
|
+
reason = 'stream-lost';
|
|
572
|
+
message =
|
|
573
|
+
`the completion budget expired while the event stream had already been failing ` +
|
|
574
|
+
`for ${Math.round(streakMs / 1000)}s; the execution may no longer exist ` +
|
|
575
|
+
`(instance deleted or record expired)`;
|
|
576
|
+
}
|
|
577
|
+
incomplete = { reason, message };
|
|
578
|
+
log('warn', message);
|
|
520
579
|
exitCode = 1;
|
|
521
580
|
timedOut = true;
|
|
522
581
|
}
|
|
@@ -552,6 +611,7 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
552
611
|
...(this.appStoreEvent ? { appstore: this.appStoreEvent } : {}),
|
|
553
612
|
...(this.playstoreEvent ? { playstore: this.playstoreEvent } : {}),
|
|
554
613
|
...(timedOut ? { timedOut } : {}),
|
|
614
|
+
...(incomplete ? { incomplete } : {}),
|
|
555
615
|
};
|
|
556
616
|
|
|
557
617
|
this.log('debug', `Execution finished: ${result.status} (exit ${result.exitCode})`);
|
|
@@ -561,75 +621,174 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
561
621
|
/**
|
|
562
622
|
* Opens an SSE connection and routes streamed events to the exposed command/stdout/stderr streams.
|
|
563
623
|
* Resolves with the exit code when an 'exitCode' event arrives.
|
|
564
|
-
* Rejects when the abort signal fires (kill or cleanup)
|
|
624
|
+
* Rejects when the abort signal fires (kill or cleanup), when the server
|
|
625
|
+
* closes the stream for good, or when the give-up clock runs out.
|
|
565
626
|
*/
|
|
566
627
|
private connectSSE(eventsUrl: string): Promise<number> {
|
|
567
628
|
return new Promise<number>((resolve, reject) => {
|
|
568
|
-
|
|
629
|
+
const signal = this.abortController.signal;
|
|
630
|
+
if (signal.aborted) {
|
|
569
631
|
reject(new Error('killed'));
|
|
570
632
|
return;
|
|
571
633
|
}
|
|
572
634
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
635
|
+
// Settle-once pair. Both paths close the source: the EventSource
|
|
636
|
+
// reconnects on its own otherwise, even after the promise is done.
|
|
637
|
+
let settled = false;
|
|
638
|
+
let connectedAt = 0;
|
|
639
|
+
let lastCycleAt = 0;
|
|
640
|
+
let lastCycleMono = 0;
|
|
641
|
+
let proofOfLifeThisCycle = false;
|
|
642
|
+
let cleanResponseThisCycle = false;
|
|
643
|
+
let lastStreamError: Error | undefined;
|
|
644
|
+
const cleanup = () => {
|
|
645
|
+
eventSource.close();
|
|
646
|
+
signal.removeEventListener('abort', onAbort);
|
|
647
|
+
};
|
|
648
|
+
const succeed = (exitCode: number) => {
|
|
649
|
+
if (settled) return;
|
|
650
|
+
settled = true;
|
|
651
|
+
cleanup();
|
|
652
|
+
resolve(exitCode);
|
|
653
|
+
};
|
|
654
|
+
const fail = (error: Error) => {
|
|
655
|
+
if (settled) return;
|
|
656
|
+
settled = true;
|
|
657
|
+
cleanup();
|
|
658
|
+
reject(error);
|
|
659
|
+
};
|
|
660
|
+
const onAbort = () => fail(new Error('killed'));
|
|
661
|
+
|
|
662
|
+
// Stream health is judged at the fetch surface, where the response
|
|
663
|
+
// status is visible; the library fires onConnect for error responses
|
|
664
|
+
// too, so an error page held open must not read as a healthy
|
|
665
|
+
// connection. An HTTP 204 makes the EventSource close for good
|
|
666
|
+
// without reporting anything, so it must fail here. A rejected fetch
|
|
667
|
+
// never reaches onDisconnect (the hazard sseFetch exists for):
|
|
668
|
+
// capture it for the give-up message and let the clock decide, so
|
|
669
|
+
// one transient refusal does not kill a live build.
|
|
670
|
+
const fetchWithStreamPolicy: Fetch = async (input, init) => {
|
|
671
|
+
const response = await nodeProxyTransport.fetch(input, init);
|
|
672
|
+
if (response.status === 204) {
|
|
673
|
+
fail(new ExecStreamClosedError(`event stream to ${eventsUrl} was closed by the server (HTTP 204)`));
|
|
674
|
+
} else if (!response.ok) {
|
|
675
|
+
lastStreamError = new Error(`server answered HTTP ${response.status}`);
|
|
676
|
+
}
|
|
677
|
+
cleanResponseThisCycle = response.ok;
|
|
678
|
+
return response;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const eventSource = createEventSource({
|
|
682
|
+
url: eventsUrl,
|
|
683
|
+
fetch: sseFetch(fetchWithStreamPolicy, (err) => {
|
|
684
|
+
lastStreamError = err instanceof Error ? err : new Error(String(err));
|
|
685
|
+
}),
|
|
686
|
+
headers: { Authorization: `Bearer ${this.options.token}` },
|
|
687
|
+
onConnect: () => {
|
|
688
|
+
connectedAt = Date.now();
|
|
689
|
+
},
|
|
690
|
+
// Comments count as proof of life, so a server-side keepalive
|
|
691
|
+
// works without a client change (onMessage never sees them).
|
|
692
|
+
onComment: () => {
|
|
693
|
+
proofOfLifeThisCycle = true;
|
|
694
|
+
},
|
|
695
|
+
// Fires once per broken cycle, before the retry timer is armed, on
|
|
696
|
+
// both failure paths (request rejected, stream ended). This is
|
|
697
|
+
// where the give-up clock runs.
|
|
698
|
+
onScheduleReconnect: () => {
|
|
699
|
+
if (settled || this.killed) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const now = Date.now();
|
|
703
|
+
const mono = performance.now();
|
|
704
|
+
const livedMs = connectedAt > 0 ? now - connectedAt : 0;
|
|
705
|
+
connectedAt = 0;
|
|
706
|
+
// Date.now() is wall clock: a laptop waking from sleep (or a
|
|
707
|
+
// clock step) would arrive with the whole window already
|
|
708
|
+
// "elapsed" and fail on its first attempt. Sleep is the wall
|
|
709
|
+
// clock advancing while the monotonic clock stands still; a
|
|
710
|
+
// large drift between the two restarts the streak. Long server
|
|
711
|
+
// retry delays and hanging connects advance both clocks equally
|
|
712
|
+
// and keep counting.
|
|
713
|
+
if (this.streamDeadSince > 0 && lastCycleAt > 0) {
|
|
714
|
+
const wallGapMs = now - lastCycleAt;
|
|
715
|
+
const monoGapMs = mono - lastCycleMono;
|
|
716
|
+
if (wallGapMs - monoGapMs > 30_000) {
|
|
717
|
+
this.streamDeadSince = now;
|
|
614
718
|
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
719
|
+
}
|
|
720
|
+
lastCycleAt = now;
|
|
721
|
+
lastCycleMono = mono;
|
|
722
|
+
const healthy =
|
|
723
|
+
proofOfLifeThisCycle ||
|
|
724
|
+
(cleanResponseThisCycle && livedMs >= sseStreamPolicy.healthyConnectionMs);
|
|
725
|
+
proofOfLifeThisCycle = false;
|
|
726
|
+
cleanResponseThisCycle = false;
|
|
727
|
+
if (healthy) {
|
|
728
|
+
this.streamDeadSince = 0;
|
|
729
|
+
lastStreamError = undefined;
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (this.streamDeadSince === 0) {
|
|
733
|
+
this.streamDeadSince = now;
|
|
734
|
+
this.log('warn', 'SSE disconnected; reconnecting');
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (now - this.streamDeadSince >= sseStreamPolicy.giveUpAfterMs) {
|
|
738
|
+
const seconds = Math.round((now - this.streamDeadSince) / 1000);
|
|
739
|
+
const cause = lastStreamError ? `; last error: ${lastStreamError.message}` : '';
|
|
740
|
+
fail(
|
|
741
|
+
new ExecStreamLostError(
|
|
742
|
+
`event stream to ${eventsUrl} kept failing for ${seconds}s without delivering events${cause}; ` +
|
|
743
|
+
'the execution may no longer exist (instance deleted or record expired)',
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
onMessage: (message: EventSourceMessage) => {
|
|
749
|
+
this.streamDeadSince = 0;
|
|
750
|
+
proofOfLifeThisCycle = true;
|
|
751
|
+
lastStreamError = undefined;
|
|
752
|
+
const data = typeof message.data === 'string' ? message.data : String(message.data ?? '');
|
|
753
|
+
const eventType = message.event;
|
|
754
|
+
if (eventType === 'command') {
|
|
755
|
+
this.command.emit('data', data);
|
|
756
|
+
} else if (eventType === 'stdout') {
|
|
757
|
+
this.stdout.emit('data', data);
|
|
758
|
+
} else if (eventType === 'stderr') {
|
|
759
|
+
this.stderr.emit('data', data);
|
|
760
|
+
} else if (eventType === 'testflight') {
|
|
761
|
+
try {
|
|
762
|
+
this.appStoreEvent = JSON.parse(data) as AppStoreEvent;
|
|
763
|
+
} catch {
|
|
764
|
+
// The wire event itself proves the server ran the App Store upload,
|
|
765
|
+
// so never let a payload glitch look like a missing feature.
|
|
766
|
+
this.appStoreEvent = { state: 'unknown' };
|
|
767
|
+
this.log('warn', `SSE testflight event has invalid data: ${data}`);
|
|
619
768
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
769
|
+
} else if (eventType === 'playstore') {
|
|
770
|
+
try {
|
|
771
|
+
this.playstoreEvent = JSON.parse(data) as PlaystoreEvent;
|
|
772
|
+
} catch {
|
|
773
|
+
// Same contract as the App Store upload event: its presence proves the server
|
|
774
|
+
// ran the Play Store step, so a payload glitch must not
|
|
775
|
+
// read as a missing feature.
|
|
776
|
+
this.playstoreEvent = { state: 'unknown' };
|
|
777
|
+
this.log('warn', `SSE playstore event has invalid data: ${data}`);
|
|
778
|
+
}
|
|
779
|
+
} else if (eventType === 'exitCode') {
|
|
780
|
+
const exitCode = parseInt(data, 10);
|
|
781
|
+
if (Number.isNaN(exitCode)) {
|
|
782
|
+
this.log('warn', `SSE exitCode event has invalid data: ${data}`);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
this.log('debug', `Execution completed via SSE: exitCode=${exitCode}`);
|
|
786
|
+
succeed(exitCode);
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
this.sseConnection = eventSource;
|
|
791
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
633
792
|
});
|
|
634
793
|
}
|
|
635
794
|
}
|
package/src/ios-client.ts
CHANGED
|
@@ -30,6 +30,8 @@ export type ConnectionStateCallback = (state: ConnectionState) => void;
|
|
|
30
30
|
const ACTIVE_RECORDING_FILENAME = 'recording.mp4';
|
|
31
31
|
export const REVERSE_TUNNEL_REMOTE_PORT_MIN = 57090;
|
|
32
32
|
export const REVERSE_TUNNEL_REMOTE_PORT_MAX = 57099;
|
|
33
|
+
/** Default tapElement timeout; see TapElementOptions.timeoutMs. */
|
|
34
|
+
export const TAP_ELEMENT_TIMEOUT_MS = 90_000;
|
|
33
35
|
|
|
34
36
|
function buildDownloadUrl(apiUrl: string): string {
|
|
35
37
|
return `${apiUrl}/files?name=${encodeURIComponent(ACTIVE_RECORDING_FILENAME)}`;
|
|
@@ -170,6 +172,25 @@ export type ScreenshotData = {
|
|
|
170
172
|
export type TapElementResult = {
|
|
171
173
|
elementLabel?: string;
|
|
172
174
|
elementType?: string;
|
|
175
|
+
/** The activation that ran. */
|
|
176
|
+
method?: TapElementActivation;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* How tapElement activates the matched element. 'touch' (the server default)
|
|
181
|
+
* scrolls the element into view when needed and synthesizes a real HID tap;
|
|
182
|
+
* 'ax' performs an accessibility press without real touch events.
|
|
183
|
+
*/
|
|
184
|
+
export type TapElementActivation = 'touch' | 'ax';
|
|
185
|
+
|
|
186
|
+
export type TapElementOptions = {
|
|
187
|
+
activate?: TapElementActivation;
|
|
188
|
+
/**
|
|
189
|
+
* Request timeout in milliseconds, default 90 seconds. The server scrolls
|
|
190
|
+
* to find off-screen elements, and that can take well over 30s on busy
|
|
191
|
+
* screens.
|
|
192
|
+
*/
|
|
193
|
+
timeoutMs?: number;
|
|
173
194
|
};
|
|
174
195
|
|
|
175
196
|
export type ElementResult = {
|
|
@@ -399,6 +420,9 @@ export type AppInstallationOptions = {
|
|
|
399
420
|
*/
|
|
400
421
|
export type PerformAction =
|
|
401
422
|
| { type: 'tap'; x: number; y: number; screenWidth?: number; screenHeight?: number }
|
|
423
|
+
// tapElement/typeText deliberately carry no activate/strategy options in
|
|
424
|
+
// batches: the server's batch decoder does not read them (use the
|
|
425
|
+
// single-shot methods for strategy control).
|
|
402
426
|
| { type: 'tapElement'; selector: AccessibilitySelector }
|
|
403
427
|
| { type: 'incrementElement'; selector: AccessibilitySelector }
|
|
404
428
|
| { type: 'decrementElement'; selector: AccessibilitySelector }
|
|
@@ -508,9 +532,12 @@ export type InstanceClient = {
|
|
|
508
532
|
/**
|
|
509
533
|
* Tap an accessibility element by selector
|
|
510
534
|
* @param selector The selector criteria to find the element
|
|
511
|
-
* @
|
|
535
|
+
* @param options Optional activation control; the server default is a real
|
|
536
|
+
* HID tap that scrolls the element into view first, `{ activate: 'ax' }`
|
|
537
|
+
* forces an accessibility press
|
|
538
|
+
* @returns Information about the tapped element, including which method ran
|
|
512
539
|
*/
|
|
513
|
-
tapElement: (selector: AccessibilitySelector) => Promise<TapElementResult>;
|
|
540
|
+
tapElement: (selector: AccessibilitySelector, options?: TapElementOptions) => Promise<TapElementResult>;
|
|
514
541
|
|
|
515
542
|
/**
|
|
516
543
|
* Increment an accessibility element (useful for sliders, steppers, etc.)
|
|
@@ -527,16 +554,21 @@ export type InstanceClient = {
|
|
|
527
554
|
decrementElement: (selector: AccessibilitySelector) => Promise<ElementResult>;
|
|
528
555
|
|
|
529
556
|
/**
|
|
530
|
-
* Set
|
|
531
|
-
*
|
|
557
|
+
* Set an element's value directly, without key events. Much faster than
|
|
558
|
+
* typing, but the app's key-event text delegates do not fire; use typeText
|
|
559
|
+
* for real typing.
|
|
532
560
|
* @param text The text value to set
|
|
533
|
-
* @param selector The selector
|
|
561
|
+
* @param selector The selector to find the element. Omit it to target the
|
|
562
|
+
* focused element; the server resolves focus.
|
|
534
563
|
* @returns Information about the modified element
|
|
535
564
|
*/
|
|
536
|
-
setElementValue: (text: string, selector
|
|
565
|
+
setElementValue: (text: string, selector?: AccessibilitySelector) => Promise<ElementResult>;
|
|
537
566
|
|
|
538
567
|
/**
|
|
539
|
-
* Type text into the
|
|
568
|
+
* Type text into the focused field as real key events, so the app's text
|
|
569
|
+
* delegates fire. Fails when no field is focused. Typing costs ~20ms per
|
|
570
|
+
* character; use setElementValue for long strings that do not need key
|
|
571
|
+
* events.
|
|
540
572
|
* @param text The text to type
|
|
541
573
|
* @param pressEnter If true, press Enter after typing
|
|
542
574
|
*/
|
|
@@ -1115,6 +1147,8 @@ type ServerResponse = {
|
|
|
1115
1147
|
duration?: number;
|
|
1116
1148
|
once?: boolean;
|
|
1117
1149
|
status?: IosMicrophoneStatus;
|
|
1150
|
+
// tapElement result fields
|
|
1151
|
+
method?: TapElementActivation;
|
|
1118
1152
|
};
|
|
1119
1153
|
|
|
1120
1154
|
/**
|
|
@@ -1724,6 +1758,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1724
1758
|
tapElementResult: (msg) => ({
|
|
1725
1759
|
elementLabel: msg.elementLabel,
|
|
1726
1760
|
elementType: msg.elementType,
|
|
1761
|
+
method: msg.method,
|
|
1727
1762
|
}),
|
|
1728
1763
|
incrementElementResult: (msg) => ({ elementLabel: msg.elementLabel }),
|
|
1729
1764
|
decrementElementResult: (msg) => ({ elementLabel: msg.elementLabel }),
|
|
@@ -2020,8 +2055,16 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
2020
2055
|
return sendRequest<void>('tap', { x, y, screenWidth, screenHeight });
|
|
2021
2056
|
};
|
|
2022
2057
|
|
|
2023
|
-
const tapElement = (
|
|
2024
|
-
|
|
2058
|
+
const tapElement = (
|
|
2059
|
+
selector: AccessibilitySelector,
|
|
2060
|
+
options?: TapElementOptions,
|
|
2061
|
+
): Promise<TapElementResult> => {
|
|
2062
|
+
return sendRequest<TapElementResult>(
|
|
2063
|
+
'tapElement',
|
|
2064
|
+
{ selector, activate: options?.activate },
|
|
2065
|
+
undefined,
|
|
2066
|
+
options?.timeoutMs ?? TAP_ELEMENT_TIMEOUT_MS,
|
|
2067
|
+
);
|
|
2025
2068
|
};
|
|
2026
2069
|
|
|
2027
2070
|
const incrementElement = (selector: AccessibilitySelector): Promise<ElementResult> => {
|
|
@@ -2032,8 +2075,16 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
2032
2075
|
return sendRequest<ElementResult>('decrementElement', { selector });
|
|
2033
2076
|
};
|
|
2034
2077
|
|
|
2035
|
-
const setElementValue = (text: string, selector
|
|
2036
|
-
|
|
2078
|
+
const setElementValue = (text: string, selector?: AccessibilitySelector): Promise<ElementResult> => {
|
|
2079
|
+
// No selector targets the focused element; the server resolves it.
|
|
2080
|
+
// JSON IPC turns undefined into null, so normalize back: the payload
|
|
2081
|
+
// must omit the selector key, not send null.
|
|
2082
|
+
const target = selector ?? undefined;
|
|
2083
|
+
return sendRequest<ElementResult>('setElementValue', {
|
|
2084
|
+
text,
|
|
2085
|
+
selector: target,
|
|
2086
|
+
focused: target ? undefined : true,
|
|
2087
|
+
});
|
|
2037
2088
|
};
|
|
2038
2089
|
|
|
2039
2090
|
const typeText = (text: string, pressEnter?: boolean): Promise<void> => {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.46.
|
|
1
|
+
export const VERSION = '0.46.9'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.9";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.9";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.46.
|
|
1
|
+
export const VERSION = '0.46.9'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|