@swmansion/argent 0.25.3-next.2 → 0.25.3-next.4
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/bin/argent-android-devtools-0.1.0.apk +0 -0
- package/bin/darwin/ax-service +0 -0
- package/bin/darwin/tvos-ax-service +0 -0
- package/bin/darwin/tvos-hid-daemon +0 -0
- package/bin/tcp/ax-service +0 -0
- package/dist/cli-cmds.mjs +74 -22
- package/dist/installer.mjs +6 -1
- package/dist/mcp-server.mjs +10 -0
- package/dist/preview-ui/index.html +63 -2
- package/dist/tool-server.cjs +2635 -1430
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
- package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
- package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +1 -1
- package/rules/argent.md +1 -1
- package/skills/argent-create-flow/references/flow-yaml.md +9 -2
- package/skills/argent-device-interact/SKILL.md +25 -1
- package/skills/argent-metro-debugger/SKILL.md +1 -1
- package/skills/argent-screen-recording/SKILL.md +1 -0
|
Binary file
|
package/bin/darwin/ax-service
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/tcp/ax-service
CHANGED
|
Binary file
|
package/dist/cli-cmds.mjs
CHANGED
|
@@ -2342,6 +2342,11 @@ var FAILURE_CODES = {
|
|
|
2342
2342
|
IOS_SETTINGS_PERMISSION_FAILED: "IOS_SETTINGS_PERMISSION_FAILED",
|
|
2343
2343
|
IOS_SHAKE_FAILED: "IOS_SHAKE_FAILED",
|
|
2344
2344
|
ANDROID_SHAKE_FAILED: "ANDROID_SHAKE_FAILED",
|
|
2345
|
+
// The hinge of a foldable iOS simulator: the server refused the request on a
|
|
2346
|
+
// foldable (a bad angle, a hinge that would not move) vs. nothing to fold —
|
|
2347
|
+
// a device that is not foldable, no hinge route, a remote simulator.
|
|
2348
|
+
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
2349
|
+
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
2345
2350
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
2346
2351
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
2347
2352
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -21872,7 +21877,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
21872
21877
|
var SESSION_ID2 = randomUUID5();
|
|
21873
21878
|
function readCliVersion() {
|
|
21874
21879
|
if (true) {
|
|
21875
|
-
return "0.25.3-next.
|
|
21880
|
+
return "0.25.3-next.4";
|
|
21876
21881
|
}
|
|
21877
21882
|
return "0.0.0";
|
|
21878
21883
|
}
|
|
@@ -23214,13 +23219,14 @@ multi-platform launch auto-detects a device instead. Pass --device to attach to
|
|
|
23214
23219
|
a running instance.
|
|
23215
23220
|
|
|
23216
23221
|
A directory run prints only the steps that need attention (each failure, each
|
|
23217
|
-
warning, and each script step's output), then its outcome
|
|
23218
|
-
|
|
23219
|
-
|
|
23220
|
-
|
|
23221
|
-
|
|
23222
|
-
|
|
23223
|
-
|
|
23222
|
+
warning, and each script step's output), then its outcome. After the last flow
|
|
23223
|
+
it lists every failed flow with its reason and a command that re-runs that flow
|
|
23224
|
+
alone, then a final flow summary; --recursive walks subdirectories too
|
|
23225
|
+
(dot-directories and node_modules are skipped). A flow that fails its steps
|
|
23226
|
+
keeps the batch running, as does one the server rejects up front \u2014 an invalid
|
|
23227
|
+
file, or a device it cannot resolve. A transport failure, a rejection the server
|
|
23228
|
+
does not mark as validation, or a reply that is not a report stops the batch and
|
|
23229
|
+
counts the remaining flows skipped.
|
|
23224
23230
|
|
|
23225
23231
|
Runs require the auto-started local tool server;
|
|
23226
23232
|
ARGENT_TOOLS_URL and \`argent link\` routing are not supported.
|
|
@@ -23306,13 +23312,15 @@ function renderEchoLine(s) {
|
|
|
23306
23312
|
}
|
|
23307
23313
|
return ` ${indent}\u203A ${s.message}`;
|
|
23308
23314
|
}
|
|
23309
|
-
function
|
|
23315
|
+
function stepLabel(s, topFlow) {
|
|
23310
23316
|
const where = s.flow && s.flow !== topFlow ? ` [${s.flow}]` : "";
|
|
23311
23317
|
const what = s.tool ?? s.target;
|
|
23312
|
-
|
|
23318
|
+
return `${what ? `${s.kind} ${what}` : s.kind}${where}`;
|
|
23319
|
+
}
|
|
23320
|
+
function renderStepLine(s, n2, topFlow) {
|
|
23313
23321
|
const reason = s.reason ? ` \u2014 ${s.reason}` : "";
|
|
23314
23322
|
const glyph = s.status === "pass" && s.warning ? "\u26A0" : STATUS_GLYPH[s.status];
|
|
23315
|
-
return ` ${glyph} ${String(n2).padStart(2)} ${stepIndent(s.depth)}${
|
|
23323
|
+
return ` ${glyph} ${String(n2).padStart(2)} ${stepIndent(s.depth)}${stepLabel(s, topFlow)}${durationSuffix(s.durationMs)}${reason}`;
|
|
23316
23324
|
}
|
|
23317
23325
|
function renderUnderStepLine(s, n2, text2) {
|
|
23318
23326
|
return `${" ".repeat(5 + Math.max(2, String(n2).length))}${stepIndent(s.depth)}${text2}`;
|
|
@@ -23376,6 +23384,35 @@ function renderFailedSteps(report) {
|
|
|
23376
23384
|
function renderBatchSummary(counts, durationMs) {
|
|
23377
23385
|
return `${counts.failed === 0 ? "PASS" : "FAIL"} \u2014 ${counts.total} flow${counts.total === 1 ? "" : "s"}: ${counts.passed} passed, ${counts.failed} failed, ${counts.skipped} skipped${durationSuffix(durationMs)}`;
|
|
23378
23386
|
}
|
|
23387
|
+
function summarizeFailure(report) {
|
|
23388
|
+
let n2 = 0;
|
|
23389
|
+
for (const s of report.steps) {
|
|
23390
|
+
if (s.kind === "echo") continue;
|
|
23391
|
+
n2++;
|
|
23392
|
+
if (s.status === "fail" || s.status === "error") {
|
|
23393
|
+
const detail = s.reason ? String(s.reason) : void 0;
|
|
23394
|
+
return { headline: `step ${n2} ${stepLabel(s, report.flow)}`, detail };
|
|
23395
|
+
}
|
|
23396
|
+
}
|
|
23397
|
+
return { headline: "failed with no failing step" };
|
|
23398
|
+
}
|
|
23399
|
+
function renderFailedFlow(f) {
|
|
23400
|
+
const lines = [` ${STATUS_GLYPH.fail} ${f.path ? `${f.path} \u203A ` : ""}${f.headline}`];
|
|
23401
|
+
for (const line of f.detail?.split("\n") ?? []) if (line) lines.push(` ${line}`);
|
|
23402
|
+
if (f.rerun) lines.push(` re-run: ${f.rerun}`);
|
|
23403
|
+
return lines;
|
|
23404
|
+
}
|
|
23405
|
+
function renderSingleFailure(report) {
|
|
23406
|
+
return report.ok ? [] : ["", ...renderFailedFlow(summarizeFailure(report))];
|
|
23407
|
+
}
|
|
23408
|
+
function renderFailedFlows(failed) {
|
|
23409
|
+
if (failed.length === 0) return [];
|
|
23410
|
+
return [
|
|
23411
|
+
"",
|
|
23412
|
+
`Failed flows (${failed.length})`,
|
|
23413
|
+
...failed.flatMap((f) => ["", ...renderFailedFlow(f)])
|
|
23414
|
+
];
|
|
23415
|
+
}
|
|
23379
23416
|
var SAFE_ARTIFACT_NAME = FLOW_NAME_PATTERN;
|
|
23380
23417
|
var SAFE_FLOW_NAME = FLOW_NAME_PATTERN;
|
|
23381
23418
|
var FLOWS_DIR = path14.join(".argent", "flows");
|
|
@@ -23384,6 +23421,17 @@ function shellQuoteArg(arg) {
|
|
|
23384
23421
|
if (SHELL_SAFE_ARG.test(arg)) return arg;
|
|
23385
23422
|
return `'${arg.replaceAll("'", "'\\''")}'`;
|
|
23386
23423
|
}
|
|
23424
|
+
function rerunCommand(flowPath, rel, projectRoot, args) {
|
|
23425
|
+
const pathArg = (p) => shellQuoteArg(p.startsWith("-") ? `.${path14.sep}${p}` : p);
|
|
23426
|
+
const fromCwd = path14.relative(projectRoot, flowPath);
|
|
23427
|
+
const shown = path14.isAbsolute(fromCwd) || fromCwd.split(/[\\/]+/).includes("..") ? flowPath : fromCwd;
|
|
23428
|
+
const parts = ["argent flow run", pathArg(shown)];
|
|
23429
|
+
if (args.device) parts.push("--device", shellQuoteArg(args.device));
|
|
23430
|
+
if (args.platform) parts.push("--platform", shellQuoteArg(args.platform));
|
|
23431
|
+
if (args.updateBaselines) parts.push("--update-baselines");
|
|
23432
|
+
if (args.output) parts.push("--output", pathArg(path14.join(args.output, path14.dirname(rel))));
|
|
23433
|
+
return parts.join(" ");
|
|
23434
|
+
}
|
|
23387
23435
|
var EXPORT_SOURCE_MARKER = ".argent-flow-source";
|
|
23388
23436
|
var MARKER_READ_RETRIES = 4;
|
|
23389
23437
|
var MARKER_READ_DELAY_MS = 5;
|
|
@@ -23541,6 +23589,7 @@ function renderReport(report) {
|
|
|
23541
23589
|
}
|
|
23542
23590
|
}
|
|
23543
23591
|
}
|
|
23592
|
+
lines.push(...renderSingleFailure(report));
|
|
23544
23593
|
lines.push(`
|
|
23545
23594
|
${renderSummary(report)}`);
|
|
23546
23595
|
return lines.join("\n");
|
|
@@ -23689,6 +23738,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
23689
23738
|
const { callTool, baseUrl } = createToolsClient({ paths: options.paths });
|
|
23690
23739
|
const outputBase = args.output ? path14.resolve(args.output) : void 0;
|
|
23691
23740
|
const results = [];
|
|
23741
|
+
const failures = [];
|
|
23692
23742
|
const batchStartedAt = Date.now();
|
|
23693
23743
|
let stopped = false;
|
|
23694
23744
|
for (const [i2, rel] of flows.entries()) {
|
|
@@ -23698,42 +23748,42 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
23698
23748
|
if (!args.json) console.log(` ${STATUS_GLYPH.skip} not run (batch stopped)`);
|
|
23699
23749
|
continue;
|
|
23700
23750
|
}
|
|
23751
|
+
const flowPath = path14.join(dir, rel);
|
|
23752
|
+
const rerun = rerunCommand(flowPath, rel, projectRoot, args);
|
|
23701
23753
|
let report;
|
|
23702
23754
|
try {
|
|
23703
|
-
const resp = await callTool(
|
|
23704
|
-
"flow-execute",
|
|
23705
|
-
buildRunPayload(path14.join(dir, rel), projectRoot, args)
|
|
23706
|
-
);
|
|
23755
|
+
const resp = await callTool("flow-execute", buildRunPayload(flowPath, projectRoot, args));
|
|
23707
23756
|
if (isFlowReport(resp.data)) report = resp.data;
|
|
23708
23757
|
} catch (err) {
|
|
23709
23758
|
const message = err instanceof Error ? err.message : String(err);
|
|
23710
23759
|
const toolErr = err instanceof ToolInvocationError ? err : void 0;
|
|
23711
23760
|
const rejectedThisFlowOnly = toolErr?.errorKind === "validation";
|
|
23712
|
-
|
|
23713
|
-
|
|
23714
|
-
` ${STATUS_GLYPH.error} ` + (rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)")
|
|
23715
|
-
);
|
|
23716
|
-
}
|
|
23761
|
+
const verdict = rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)";
|
|
23762
|
+
if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
|
|
23717
23763
|
console.error(message);
|
|
23718
23764
|
results.push({ path: rel, status: "fail", error: message, ...failureSignal2(err) });
|
|
23765
|
+
failures.push({ path: rel, headline: verdict, detail: message, rerun });
|
|
23719
23766
|
if (!rejectedThisFlowOnly) stopped = true;
|
|
23720
23767
|
continue;
|
|
23721
23768
|
}
|
|
23722
23769
|
if (!report) {
|
|
23723
23770
|
const message = `"${rel}" did not produce a run report.`;
|
|
23724
|
-
|
|
23771
|
+
const verdict = "did not finish (no run report)";
|
|
23772
|
+
if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
|
|
23725
23773
|
console.error(message);
|
|
23726
23774
|
results.push({ path: rel, status: "fail", error: message });
|
|
23775
|
+
failures.push({ path: rel, headline: verdict, detail: message, rerun });
|
|
23727
23776
|
stopped = true;
|
|
23728
23777
|
continue;
|
|
23729
23778
|
}
|
|
23730
23779
|
await exportAndResolveArtifacts(
|
|
23731
23780
|
report,
|
|
23732
23781
|
outputBase ? path14.join(outputBase, path14.dirname(rel)) : void 0,
|
|
23733
|
-
|
|
23782
|
+
flowPath,
|
|
23734
23783
|
baseUrl
|
|
23735
23784
|
);
|
|
23736
23785
|
results.push({ path: rel, status: report.ok ? "pass" : "fail", report });
|
|
23786
|
+
if (!report.ok) failures.push({ path: rel, ...summarizeFailure(report), rerun });
|
|
23737
23787
|
if (!args.json) {
|
|
23738
23788
|
for (const line of renderFailedSteps(report)) console.log(line);
|
|
23739
23789
|
console.log(` ${renderSummary(report, { withDevice: true })}`);
|
|
@@ -23751,6 +23801,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
23751
23801
|
JSON.stringify({ ok: counts.failed === 0, ...counts, durationMs, flows: results }, null, 2)
|
|
23752
23802
|
);
|
|
23753
23803
|
} else {
|
|
23804
|
+
for (const line of renderFailedFlows(failures)) console.log(line);
|
|
23754
23805
|
console.log(`
|
|
23755
23806
|
${renderBatchSummary(counts, durationMs)}`);
|
|
23756
23807
|
}
|
|
@@ -23983,6 +24034,7 @@ ${recovery}`,
|
|
|
23983
24034
|
} else if (liveSteps > 0) {
|
|
23984
24035
|
if (report.executionPrerequisite) console.log(` assumes: ${report.executionPrerequisite}`);
|
|
23985
24036
|
for (const line of renderArtifactLines(report)) console.log(line);
|
|
24037
|
+
for (const line of renderSingleFailure(report)) console.log(line);
|
|
23986
24038
|
console.log(`
|
|
23987
24039
|
${renderSummary(report, { withDevice: true })}`);
|
|
23988
24040
|
} else {
|
package/dist/installer.mjs
CHANGED
|
@@ -15727,6 +15727,11 @@ var FAILURE_CODES = {
|
|
|
15727
15727
|
IOS_SETTINGS_PERMISSION_FAILED: "IOS_SETTINGS_PERMISSION_FAILED",
|
|
15728
15728
|
IOS_SHAKE_FAILED: "IOS_SHAKE_FAILED",
|
|
15729
15729
|
ANDROID_SHAKE_FAILED: "ANDROID_SHAKE_FAILED",
|
|
15730
|
+
// The hinge of a foldable iOS simulator: the server refused the request on a
|
|
15731
|
+
// foldable (a bad angle, a hinge that would not move) vs. nothing to fold —
|
|
15732
|
+
// a device that is not foldable, no hinge route, a remote simulator.
|
|
15733
|
+
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
15734
|
+
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
15730
15735
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
15731
15736
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
15732
15737
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -16725,7 +16730,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
16725
16730
|
var SESSION_ID = randomUUID4();
|
|
16726
16731
|
function readCliVersion() {
|
|
16727
16732
|
if (true) {
|
|
16728
|
-
return "0.25.3-next.
|
|
16733
|
+
return "0.25.3-next.4";
|
|
16729
16734
|
}
|
|
16730
16735
|
return "0.0.0";
|
|
16731
16736
|
}
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -16522,6 +16522,11 @@ var FAILURE_CODES = {
|
|
|
16522
16522
|
IOS_SETTINGS_PERMISSION_FAILED: "IOS_SETTINGS_PERMISSION_FAILED",
|
|
16523
16523
|
IOS_SHAKE_FAILED: "IOS_SHAKE_FAILED",
|
|
16524
16524
|
ANDROID_SHAKE_FAILED: "ANDROID_SHAKE_FAILED",
|
|
16525
|
+
// The hinge of a foldable iOS simulator: the server refused the request on a
|
|
16526
|
+
// foldable (a bad angle, a hinge that would not move) vs. nothing to fold —
|
|
16527
|
+
// a device that is not foldable, no hinge route, a remote simulator.
|
|
16528
|
+
IOS_FOLD_FAILED: "IOS_FOLD_FAILED",
|
|
16529
|
+
IOS_FOLD_UNSUPPORTED: "IOS_FOLD_UNSUPPORTED",
|
|
16525
16530
|
PASTE_CLIPBOARD_SET_FAILED: "PASTE_CLIPBOARD_SET_FAILED",
|
|
16526
16531
|
PASTE_CLIPBOARD_UNSUPPORTED: "PASTE_CLIPBOARD_UNSUPPORTED",
|
|
16527
16532
|
SETTINGS_PERMISSION_UNSUPPORTED: "SETTINGS_PERMISSION_UNSUPPORTED",
|
|
@@ -18639,6 +18644,7 @@ var AUTO_SCREENSHOT_TOOLS = /* @__PURE__ */ new Set([
|
|
|
18639
18644
|
"keyboard",
|
|
18640
18645
|
"paste",
|
|
18641
18646
|
"rotate",
|
|
18647
|
+
"fold",
|
|
18642
18648
|
"launch-app",
|
|
18643
18649
|
"restart-app",
|
|
18644
18650
|
"open-url",
|
|
@@ -18659,6 +18665,9 @@ var AUTO_SCREENSHOT_DELAY_MS_BY_TOOL = {
|
|
|
18659
18665
|
"run-sequence": 15e3,
|
|
18660
18666
|
"button": 1500,
|
|
18661
18667
|
"rotate": 1e3,
|
|
18668
|
+
// The fold tool already waits for the panel hand-over; the cap covers the
|
|
18669
|
+
// repaint after it.
|
|
18670
|
+
"fold": 1500,
|
|
18662
18671
|
"keyboard": 300,
|
|
18663
18672
|
"paste": 300,
|
|
18664
18673
|
"describe": 100
|
|
@@ -18679,6 +18688,7 @@ var AUTO_DESCRIBE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
18679
18688
|
"keyboard",
|
|
18680
18689
|
"paste",
|
|
18681
18690
|
"rotate",
|
|
18691
|
+
"fold",
|
|
18682
18692
|
"launch-app",
|
|
18683
18693
|
"restart-app",
|
|
18684
18694
|
"open-url",
|
|
@@ -1976,6 +1976,16 @@
|
|
|
1976
1976
|
let ws = null;
|
|
1977
1977
|
let wsOpen = false;
|
|
1978
1978
|
let currentUdid = null;
|
|
1979
|
+
// A foldable's panel: the CoreSimulator screen id its stream shows and
|
|
1980
|
+
// its touches name (1 the cover panel, 3 the inner one on the iPhone
|
|
1981
|
+
// Duo). null for a device with one panel, where commands name none.
|
|
1982
|
+
// This page's touches and wheels go to the simulator-server directly and
|
|
1983
|
+
// name this value, which the panel poll below refreshes every 1.5 s: for
|
|
1984
|
+
// up to that long after a fold, a preview touch can go to the panel that
|
|
1985
|
+
// went dark. An accepted limitation of the manual preview; the agent
|
|
1986
|
+
// tools resolve the panel on every touch.
|
|
1987
|
+
let activeScreen = null;
|
|
1988
|
+
let panelPollTimer = null;
|
|
1979
1989
|
let img = null;
|
|
1980
1990
|
let stage = null;
|
|
1981
1991
|
// Auto-reconnect bookkeeping (see scheduleReconnect): the simulator-server
|
|
@@ -2021,7 +2031,14 @@
|
|
|
2021
2031
|
// While the element-comment inspector is active the stream is a
|
|
2022
2032
|
// read-only annotation surface — never drive the device.
|
|
2023
2033
|
if (typeof vp !== "undefined" && vp.inspect) return;
|
|
2024
|
-
|
|
2034
|
+
const payload = { id: nextCmdId(), ...obj };
|
|
2035
|
+
// simulator-server captures every panel of a foldable and follows
|
|
2036
|
+
// none: a touch or wheel that names no screen goes to the cover panel,
|
|
2037
|
+
// black once the device is open.
|
|
2038
|
+
if (activeScreen !== null && (obj.cmd === "touch" || obj.cmd === "wheel")) {
|
|
2039
|
+
payload.screen = activeScreen;
|
|
2040
|
+
}
|
|
2041
|
+
ws.send(JSON.stringify(payload));
|
|
2025
2042
|
// Every command (touch/scroll/key) likely moved the screen — wake the
|
|
2026
2043
|
// describe loop *now* so connectors/pins track interaction, not just
|
|
2027
2044
|
// the fixed cadence. Only when something is actually anchored to the
|
|
@@ -2311,6 +2328,8 @@
|
|
|
2311
2328
|
// /preview/describe/<oldUdid> after teardown.
|
|
2312
2329
|
setDeviceIndicator(null);
|
|
2313
2330
|
currentUdid = null;
|
|
2331
|
+
stopPanelPoll();
|
|
2332
|
+
activeScreen = null;
|
|
2314
2333
|
describeWarmupPending = false;
|
|
2315
2334
|
// Explicitly clear the img src — otherwise the browser keeps the MJPEG
|
|
2316
2335
|
// HTTP connection open after the element is detached, and re-selecting
|
|
@@ -2338,6 +2357,46 @@
|
|
|
2338
2357
|
// route) heals it instead of stranding the user on a refresh. The
|
|
2339
2358
|
// reconnectTimer guard collapses the simultaneous img.onerror + ws.onclose
|
|
2340
2359
|
// into a single attempt; backoff resets on a successful ws.onopen.
|
|
2360
|
+
// A foldable's stream URL already carries `?screen=<id>`.
|
|
2361
|
+
function withCacheBuster(url) {
|
|
2362
|
+
return `${url}${url.includes("?") ? "&" : "?"}t=${Date.now()}`;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
function stopPanelPoll() {
|
|
2366
|
+
if (panelPollTimer) {
|
|
2367
|
+
clearInterval(panelPollTimer);
|
|
2368
|
+
panelPollTimer = null;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// Follow a foldable across folds: re-ask the tool-server which panel the
|
|
2373
|
+
// device renders to, and move the stream and the touches when it changes.
|
|
2374
|
+
function startPanelPoll(udid) {
|
|
2375
|
+
stopPanelPoll();
|
|
2376
|
+
let inFlight = false;
|
|
2377
|
+
panelPollTimer = setInterval(async () => {
|
|
2378
|
+
if (inFlight || currentUdid !== udid || !img) return;
|
|
2379
|
+
inFlight = true;
|
|
2380
|
+
try {
|
|
2381
|
+
const res = await fetch(
|
|
2382
|
+
`${TOOL_SERVER}/preview/simulator-server/${encodeURIComponent(udid)}`
|
|
2383
|
+
);
|
|
2384
|
+
if (!res.ok) return;
|
|
2385
|
+
const info = await res.json();
|
|
2386
|
+
if (currentUdid !== udid || !img || !info.foldable) return;
|
|
2387
|
+
if (info.activeScreen !== activeScreen) {
|
|
2388
|
+
activeScreen = info.activeScreen;
|
|
2389
|
+
img.src = withCacheBuster(info.streamUrl);
|
|
2390
|
+
setStatus("");
|
|
2391
|
+
}
|
|
2392
|
+
} catch {
|
|
2393
|
+
/* transient; the next tick asks again */
|
|
2394
|
+
} finally {
|
|
2395
|
+
inFlight = false;
|
|
2396
|
+
}
|
|
2397
|
+
}, 1500);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2341
2400
|
function scheduleReconnect(udid) {
|
|
2342
2401
|
if (currentUdid !== udid || reconnectTimer) return;
|
|
2343
2402
|
const delay = Math.min(1000 * 2 ** reconnectAttempts, 8000);
|
|
@@ -2428,7 +2487,9 @@
|
|
|
2428
2487
|
img.alt = `Simulator ${udid.slice(0, 8)}`;
|
|
2429
2488
|
img.draggable = false;
|
|
2430
2489
|
// Append a cache-buster so re-connects don't reuse a stale MJPEG stream.
|
|
2431
|
-
img.src =
|
|
2490
|
+
img.src = withCacheBuster(info.streamUrl);
|
|
2491
|
+
activeScreen = info.foldable ? info.activeScreen : null;
|
|
2492
|
+
if (info.foldable) startPanelPoll(udid);
|
|
2432
2493
|
img.onerror = () => {
|
|
2433
2494
|
setStatus("stream error", "error");
|
|
2434
2495
|
scheduleReconnect(udid);
|