execbro 2.15.0 → 2.16.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.
Files changed (93) hide show
  1. package/README.md +5 -0
  2. package/build/__tests__/helpers/fakeNativeSdk.d.ts +19 -0
  3. package/build/__tests__/helpers/fakeNativeSdk.d.ts.map +1 -0
  4. package/build/__tests__/helpers/fakeNativeSdk.js +53 -0
  5. package/build/__tests__/helpers/fakeNativeSdk.js.map +1 -0
  6. package/build/__tests__/unit/nativeApp.test.d.ts +2 -0
  7. package/build/__tests__/unit/nativeApp.test.d.ts.map +1 -0
  8. package/build/__tests__/unit/nativeApp.test.js +77 -0
  9. package/build/__tests__/unit/nativeApp.test.js.map +1 -0
  10. package/build/__tests__/unit/nativeConnectRace.test.d.ts +2 -0
  11. package/build/__tests__/unit/nativeConnectRace.test.d.ts.map +1 -0
  12. package/build/__tests__/unit/nativeConnectRace.test.js +33 -0
  13. package/build/__tests__/unit/nativeConnectRace.test.js.map +1 -0
  14. package/build/__tests__/unit/nativeConnection.test.d.ts +2 -0
  15. package/build/__tests__/unit/nativeConnection.test.d.ts.map +1 -0
  16. package/build/__tests__/unit/nativeConnection.test.js +78 -0
  17. package/build/__tests__/unit/nativeConnection.test.js.map +1 -0
  18. package/build/__tests__/unit/nativeDiscovery.test.d.ts +2 -0
  19. package/build/__tests__/unit/nativeDiscovery.test.d.ts.map +1 -0
  20. package/build/__tests__/unit/nativeDiscovery.test.js +67 -0
  21. package/build/__tests__/unit/nativeDiscovery.test.js.map +1 -0
  22. package/build/__tests__/unit/nativeEnsureConnection.test.d.ts +2 -0
  23. package/build/__tests__/unit/nativeEnsureConnection.test.d.ts.map +1 -0
  24. package/build/__tests__/unit/nativeEnsureConnection.test.js +27 -0
  25. package/build/__tests__/unit/nativeEnsureConnection.test.js.map +1 -0
  26. package/build/__tests__/unit/nativeGate.test.d.ts +2 -0
  27. package/build/__tests__/unit/nativeGate.test.d.ts.map +1 -0
  28. package/build/__tests__/unit/nativeGate.test.js +85 -0
  29. package/build/__tests__/unit/nativeGate.test.js.map +1 -0
  30. package/build/__tests__/unit/nativeGuide.test.d.ts +2 -0
  31. package/build/__tests__/unit/nativeGuide.test.d.ts.map +1 -0
  32. package/build/__tests__/unit/nativeGuide.test.js +16 -0
  33. package/build/__tests__/unit/nativeGuide.test.js.map +1 -0
  34. package/build/__tests__/unit/nativeLogs.test.js +28 -0
  35. package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
  36. package/build/__tests__/unit/nativeNavigate.test.d.ts +2 -0
  37. package/build/__tests__/unit/nativeNavigate.test.d.ts.map +1 -0
  38. package/build/__tests__/unit/nativeNavigate.test.js +85 -0
  39. package/build/__tests__/unit/nativeNavigate.test.js.map +1 -0
  40. package/build/core/connection.d.ts +2 -0
  41. package/build/core/connection.d.ts.map +1 -1
  42. package/build/core/connection.js +21 -9
  43. package/build/core/connection.js.map +1 -1
  44. package/build/core/guides.d.ts.map +1 -1
  45. package/build/core/guides.js +39 -1
  46. package/build/core/guides.js.map +1 -1
  47. package/build/core/nativeApp.d.ts +12 -0
  48. package/build/core/nativeApp.d.ts.map +1 -0
  49. package/build/core/nativeApp.js +93 -0
  50. package/build/core/nativeApp.js.map +1 -0
  51. package/build/core/nativeCapabilities.d.ts +11 -0
  52. package/build/core/nativeCapabilities.d.ts.map +1 -0
  53. package/build/core/nativeCapabilities.js +27 -0
  54. package/build/core/nativeCapabilities.js.map +1 -0
  55. package/build/core/nativeConnection.d.ts +17 -0
  56. package/build/core/nativeConnection.d.ts.map +1 -0
  57. package/build/core/nativeConnection.js +144 -0
  58. package/build/core/nativeConnection.js.map +1 -0
  59. package/build/core/nativeDiscovery.d.ts +19 -0
  60. package/build/core/nativeDiscovery.d.ts.map +1 -0
  61. package/build/core/nativeDiscovery.js +74 -0
  62. package/build/core/nativeDiscovery.js.map +1 -0
  63. package/build/core/nativeLogs.d.ts.map +1 -1
  64. package/build/core/nativeLogs.js +3 -2
  65. package/build/core/nativeLogs.js.map +1 -1
  66. package/build/core/register.d.ts.map +1 -1
  67. package/build/core/register.js +7 -2
  68. package/build/core/register.js.map +1 -1
  69. package/build/core/sdkMirrorPoller.d.ts.map +1 -1
  70. package/build/core/sdkMirrorPoller.js +2 -0
  71. package/build/core/sdkMirrorPoller.js.map +1 -1
  72. package/build/core/types.d.ts +3 -0
  73. package/build/core/types.d.ts.map +1 -1
  74. package/build/pro/tap.d.ts.map +1 -1
  75. package/build/pro/tap.js +2 -2
  76. package/build/pro/tap.js.map +1 -1
  77. package/build/tools/bundleTools.d.ts.map +1 -1
  78. package/build/tools/bundleTools.js +1 -1
  79. package/build/tools/bundleTools.js.map +1 -1
  80. package/build/tools/connectionTools.d.ts.map +1 -1
  81. package/build/tools/connectionTools.js +23 -5
  82. package/build/tools/connectionTools.js.map +1 -1
  83. package/build/tools/executionTools.d.ts.map +1 -1
  84. package/build/tools/executionTools.js +41 -3
  85. package/build/tools/executionTools.js.map +1 -1
  86. package/build/tools/navigationTools.d.ts.map +1 -1
  87. package/build/tools/navigationTools.js +6 -7
  88. package/build/tools/navigationTools.js.map +1 -1
  89. package/build/tools/networkTools.d.ts.map +1 -1
  90. package/build/tools/networkTools.js +2 -0
  91. package/build/tools/networkTools.js.map +1 -1
  92. package/package.json +1 -1
  93. package/tools.json +7 -1
package/README.md CHANGED
@@ -78,6 +78,11 @@ Linking is only for the dashboard: it attaches your installs to your account so
78
78
 
79
79
  Keep the `!app.isPackaged` guard: an open debugging port on a packaged build lets anything that can reach it run code in your renderer
80
80
 
81
+ ### Native Android (preview)
82
+
83
+ - **Kotlin apps without a JavaScript runtime** - A native app that includes `execbro-sdk-android` in its debug build registers named state and commands. `scan_metro` finds it over adb, `list_debug_globals` and `inspect_global` read its state, `run_app_command` runs its commands, `navigate` drives Jetpack Navigation, and `screenshot` captures the screen
84
+ - **Coming soon** - The SDK is not published to Maven yet. Execbro already supports it; install instructions will follow with the SDK release
85
+
81
86
  ### Under the Hood
82
87
 
83
88
  - **Auto-Discovery** - Scans Metro on ports 8081-8090 automatically, and lists Chromium/Electron targets on 9222 (plus any ports in `EXECBRO_CHROMIUM_PORTS`)
@@ -0,0 +1,19 @@
1
+ export declare const NO_REPLY: unique symbol;
2
+ export type FakeHandler = (method: string, params: Record<string, unknown>) => unknown;
3
+ export declare const HELLO: {
4
+ protocolVersion: number;
5
+ sdkVersion: string;
6
+ package: string;
7
+ label: string;
8
+ versionName: string;
9
+ capabilities: string[];
10
+ };
11
+ export declare function rpcError(code: string, message: string): Error;
12
+ export declare function startFakeSdk(handler: FakeHandler): Promise<{
13
+ port: number;
14
+ url: string;
15
+ emit(method: string, params: unknown): void;
16
+ dropClients(): void;
17
+ close(): Promise<void>;
18
+ }>;
19
+ //# sourceMappingURL=fakeNativeSdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeNativeSdk.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/fakeNativeSdk.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAC3C,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;AAEvF,eAAO,MAAM,KAAK;;;;;;;CAOjB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAE7D;AAGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,WAAW;;;iBAsBlC,MAAM,UAAU,OAAO;;aAM3B,OAAO,CAAC,IAAI,CAAC;GAK7B"}
@@ -0,0 +1,53 @@
1
+ import { WebSocketServer } from "ws";
2
+ export const NO_REPLY = Symbol("no-reply");
3
+ export const HELLO = {
4
+ protocolVersion: 1,
5
+ sdkVersion: "0.1.0",
6
+ package: "com.example.app",
7
+ label: "Example",
8
+ versionName: "1.0",
9
+ capabilities: ["state", "command", "navigation"]
10
+ };
11
+ export function rpcError(code, message) {
12
+ return Object.assign(new Error(message), { code });
13
+ }
14
+ export async function startFakeSdk(handler) {
15
+ const server = new WebSocketServer({ port: 0, host: "127.0.0.1" });
16
+ await new Promise((resolve) => server.once("listening", () => resolve()));
17
+ const sockets = [];
18
+ server.on("connection", (ws) => {
19
+ sockets.push(ws);
20
+ ws.on("message", (data) => {
21
+ const req = JSON.parse(data.toString());
22
+ try {
23
+ const result = handler(req.method, req.params ?? {});
24
+ if (result === NO_REPLY)
25
+ return;
26
+ ws.send(JSON.stringify({ id: req.id, result }));
27
+ }
28
+ catch (e) {
29
+ const err = e;
30
+ ws.send(JSON.stringify({ id: req.id, error: { code: err.code ?? "THREW", message: err.message } }));
31
+ }
32
+ });
33
+ });
34
+ const port = server.address().port;
35
+ return {
36
+ port,
37
+ url: `ws://127.0.0.1:${port}`,
38
+ emit(method, params) {
39
+ for (const s of sockets)
40
+ s.send(JSON.stringify({ method, params }));
41
+ },
42
+ dropClients() {
43
+ for (const s of sockets)
44
+ s.terminate();
45
+ },
46
+ close() {
47
+ for (const s of sockets)
48
+ s.terminate();
49
+ return new Promise((resolve) => server.close(() => resolve()));
50
+ }
51
+ };
52
+ }
53
+ //# sourceMappingURL=fakeNativeSdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeNativeSdk.js","sourceRoot":"","sources":["../../../src/__tests__/helpers/fakeNativeSdk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeApp.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeApp.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeApp.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, afterEach, beforeEach } from "@jest/globals";
2
+ import { connectedApps } from "../../core/state.js";
3
+ import { openNativeSocket } from "../../core/nativeConnection.js";
4
+ import { nativeListDebugGlobals, nativeInspectGlobal, nativeRunCommand } from "../../core/nativeApp.js";
5
+ import { startFakeSdk, rpcError, HELLO } from "../helpers/fakeNativeSdk.js";
6
+ let fake;
7
+ let app;
8
+ let locked = false;
9
+ beforeEach(async () => {
10
+ locked = false;
11
+ fake = await startFakeSdk((method, params) => {
12
+ switch (method) {
13
+ case "execbro.hello": return HELLO;
14
+ case "state.list": return [{ name: "lock" }, { name: "prefs" }];
15
+ case "command.list": return [{ name: "lock", description: "Engage the touch lock" }, { name: "resetPrefs", description: "" }];
16
+ case "state.get":
17
+ if (params.name === "lock")
18
+ return { value: locked };
19
+ if (params.name === "prefs")
20
+ return { value: { posX: 0, allowedPackages: ["com.google.android.youtube"] } };
21
+ throw rpcError("NOT_FOUND", `No state named '${String(params.name)}'. Registered: lock, prefs`);
22
+ case "command.run":
23
+ if (params.name === "lock") {
24
+ locked = true;
25
+ return { result: "locked" };
26
+ }
27
+ throw rpcError("THREW", "java.lang.IllegalStateException: Bubble service is not running");
28
+ default: throw rpcError("METHOD_NOT_FOUND", method);
29
+ }
30
+ });
31
+ app = await openNativeSocket(fake.url, { serial: "emulator-5554", pkg: "com.example.app", port: fake.port });
32
+ });
33
+ afterEach(async () => {
34
+ for (const a of connectedApps.values())
35
+ a.ws.terminate();
36
+ connectedApps.clear();
37
+ await fake.close();
38
+ });
39
+ describe("nativeListDebugGlobals", () => {
40
+ it("lists state and commands in two groups", async () => {
41
+ const r = await nativeListDebugGlobals(app);
42
+ expect(r.success).toBe(true);
43
+ expect(r.result).toContain("State (read with inspect_global");
44
+ expect(r.result).toContain(" - prefs");
45
+ expect(r.result).toContain("Commands (run with run_app_command");
46
+ expect(r.result).toContain(" - lock: Engage the touch lock");
47
+ expect(r.result).toContain(" - resetPrefs");
48
+ });
49
+ });
50
+ describe("nativeInspectGlobal", () => {
51
+ it("returns the value as JSON", async () => {
52
+ const r = await nativeInspectGlobal(app, "prefs");
53
+ expect(JSON.parse(r.result)).toEqual({ posX: 0, allowedPackages: ["com.google.android.youtube"] });
54
+ });
55
+ it("reports a missing name with the SDK's code and list", async () => {
56
+ const r = await nativeInspectGlobal(app, "nope");
57
+ expect(r.success).toBe(false);
58
+ expect(r.error).toBe("NOT_FOUND: No state named 'nope'. Registered: lock, prefs");
59
+ });
60
+ });
61
+ describe("nativeRunCommand", () => {
62
+ it("lists commands when no name is given", async () => {
63
+ const r = await nativeRunCommand(app);
64
+ expect(r.result).toBe("- lock: Engage the touch lock\n- resetPrefs");
65
+ });
66
+ it("runs a command and the state reflects it", async () => {
67
+ expect((await nativeRunCommand(app, "lock")).result).toBe('lock -> "locked"');
68
+ expect((await nativeInspectGlobal(app, "lock")).result).toBe("true");
69
+ });
70
+ it("surfaces an app-side exception", async () => {
71
+ const r = await nativeRunCommand(app, "unlock");
72
+ expect(r.success).toBe(false);
73
+ expect(r.error).toContain("THREW");
74
+ expect(r.error).toContain("Bubble service is not running");
75
+ });
76
+ });
77
+ //# sourceMappingURL=nativeApp.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeApp.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeApp.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeConnectRace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeConnectRace.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeConnectRace.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect, jest, afterEach } from "@jest/globals";
2
+ import { startFakeSdk, HELLO } from "../helpers/fakeNativeSdk.js";
3
+ const execFileAsyncMock = jest.fn();
4
+ jest.unstable_mockModule("../../core/exec.js", () => ({
5
+ execFileAsync: execFileAsyncMock,
6
+ execAsync: async () => ({ stdout: "", stderr: "" }),
7
+ quoteForDeviceShell: (v) => v,
8
+ withCancelableTimeout: (fn) => fn(undefined)
9
+ }));
10
+ const { connectNativeApp } = await import("../../core/nativeConnection.js");
11
+ const { connectedApps } = await import("../../core/state.js");
12
+ let fake = null;
13
+ afterEach(async () => {
14
+ for (const app of connectedApps.values())
15
+ app.ws.terminate();
16
+ connectedApps.clear();
17
+ await fake?.close();
18
+ fake = null;
19
+ });
20
+ describe("connectNativeApp", () => {
21
+ it("shares one connection between concurrent connects to the same app", async () => {
22
+ fake = await startFakeSdk((m) => (m === "execbro.hello" ? HELLO : null));
23
+ const port = fake.port;
24
+ execFileAsyncMock.mockImplementation(async (_file, args) => ({ stdout: args.includes("tcp:0") ? `${port}\n` : "", stderr: "" }));
25
+ const [a, b] = await Promise.all([
26
+ connectNativeApp("emulator-5554", "com.example.app"),
27
+ connectNativeApp("emulator-5554", "com.example.app")
28
+ ]);
29
+ expect(a === b).toBe(true);
30
+ expect(execFileAsyncMock.mock.calls.filter(([, args]) => args.includes("tcp:0"))).toHaveLength(1);
31
+ });
32
+ });
33
+ //# sourceMappingURL=nativeConnectRace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeConnectRace.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeConnectRace.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,iBAAiB,GAAG,IAAI,CAAC,EAAE,EAAiF,CAAC;AACnH,IAAI,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClD,aAAa,EAAE,iBAAiB;IAChC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACnD,mBAAmB,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC;IACrC,qBAAqB,EAAE,CAAC,EAAqC,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC;CAClF,CAAC,CAAC,CAAC;AAEJ,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAAC;AAC5E,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAE9D,IAAI,IAAI,GAAoD,IAAI,CAAC;AAEjE,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;IAC7D,aAAa,CAAC,KAAK,EAAE,CAAC;IACtB,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;IACpB,IAAI,GAAG,IAAI,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QAC/E,IAAI,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,iBAAiB,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAEjI,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC7B,gBAAgB,CAAC,eAAe,EAAE,iBAAiB,CAAC;YACpD,gBAAgB,CAAC,eAAe,EAAE,iBAAiB,CAAC;SACvD,CAAC,CAAC;QAEH,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtG,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeConnection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeConnection.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeConnection.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect, afterEach } from "@jest/globals";
2
+ import { connectedApps } from "../../core/state.js";
3
+ import { openNativeSocket, nativeRequest, nativeAppKey, NativeRpcError } from "../../core/nativeConnection.js";
4
+ import { startFakeSdk, rpcError, NO_REPLY, HELLO } from "../helpers/fakeNativeSdk.js";
5
+ const target = { serial: "emulator-5554", pkg: "com.example.app", port: 0 };
6
+ let fake = null;
7
+ afterEach(async () => {
8
+ for (const app of connectedApps.values())
9
+ app.ws.terminate();
10
+ connectedApps.clear();
11
+ await fake?.close();
12
+ fake = null;
13
+ });
14
+ function handler(method, params) {
15
+ if (method === "execbro.hello")
16
+ return HELLO;
17
+ if (method === "state.get" && params.name === "lock")
18
+ return { value: true };
19
+ if (method === "state.get")
20
+ throw rpcError("NOT_FOUND", `No state named '${String(params.name)}'`);
21
+ if (method === "slow")
22
+ return NO_REPLY;
23
+ throw rpcError("METHOD_NOT_FOUND", method);
24
+ }
25
+ describe("openNativeSocket", () => {
26
+ it("registers the app under its native key", async () => {
27
+ fake = await startFakeSdk(handler);
28
+ const app = await openNativeSocket(fake.url, { ...target, port: fake.port });
29
+ expect(nativeAppKey("emulator-5554", "com.example.app")).toBe("native:emulator-5554:com.example.app");
30
+ expect(connectedApps.get("native:emulator-5554:com.example.app")).toBe(app);
31
+ expect(app.runtime).toBe("native");
32
+ expect(app.platform).toBe("android");
33
+ expect(app.adbSerial).toBe("emulator-5554");
34
+ expect(app.nativePackage).toBe("com.example.app");
35
+ expect(app.deviceInfo.deviceName).toBe("Example");
36
+ expect(app.nativeCapabilities).toEqual(["state", "command", "navigation"]);
37
+ });
38
+ it("refuses an SDK speaking another protocol version and registers nothing", async () => {
39
+ fake = await startFakeSdk((m) => (m === "execbro.hello" ? { ...HELLO, protocolVersion: 2 } : null));
40
+ await expect(openNativeSocket(fake.url, { ...target, port: fake.port })).rejects.toThrow(/protocol 2/);
41
+ expect(connectedApps.size).toBe(0);
42
+ });
43
+ it("drops the app, rejects pending calls and reports the close when the socket goes away", async () => {
44
+ fake = await startFakeSdk(handler);
45
+ let closed = false;
46
+ const app = await openNativeSocket(fake.url, { ...target, port: fake.port }, () => { closed = true; });
47
+ const pending = nativeRequest(app.ws, "slow");
48
+ fake.dropClients();
49
+ await expect(pending).rejects.toMatchObject({ code: "DISCONNECTED" });
50
+ await new Promise((r) => setTimeout(r, 20));
51
+ expect(connectedApps.size).toBe(0);
52
+ expect(closed).toBe(true);
53
+ });
54
+ it("survives socket errors after the connection is open", async () => {
55
+ fake = await startFakeSdk(handler);
56
+ const app = await openNativeSocket(fake.url, { ...target, port: fake.port });
57
+ expect(() => {
58
+ app.ws.emit("error", new Error("ECONNRESET"));
59
+ app.ws.emit("error", new Error("ECONNRESET"));
60
+ }).not.toThrow();
61
+ });
62
+ });
63
+ describe("nativeRequest", () => {
64
+ it("resolves results and rejects errors with their code", async () => {
65
+ fake = await startFakeSdk(handler);
66
+ const app = await openNativeSocket(fake.url, { ...target, port: fake.port });
67
+ await expect(nativeRequest(app.ws, "state.get", { name: "lock" })).resolves.toEqual({ value: true });
68
+ const err = (await nativeRequest(app.ws, "state.get", { name: "nope" }).catch((e) => e));
69
+ expect(err).toBeInstanceOf(NativeRpcError);
70
+ expect(err.code).toBe("NOT_FOUND");
71
+ });
72
+ it("times out a call the app never answers", async () => {
73
+ fake = await startFakeSdk(handler);
74
+ const app = await openNativeSocket(fake.url, { ...target, port: fake.port });
75
+ await expect(nativeRequest(app.ws, "slow", undefined, 50)).rejects.toMatchObject({ code: "TIMEOUT" });
76
+ });
77
+ });
78
+ //# sourceMappingURL=nativeConnection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeConnection.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeConnection.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeDiscovery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeDiscovery.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeDiscovery.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { parseExecbroSockets, discoverAndConnectNativeApps, connectNativePackage } from "../../core/nativeDiscovery.js";
3
+ const PROC_NET_UNIX = [
4
+ "Num RefCount Protocol Flags Type St Inode Path",
5
+ "0000000000000000: 00000002 00000000 00010000 0001 01 41234 @execbro_com.ihorzheludkov.touchdisabler",
6
+ "0000000000000000: 00000003 00000000 00000000 0001 03 41240 @execbro_com.ihorzheludkov.touchdisabler",
7
+ "0000000000000000: 00000002 00000000 00010000 0001 01 51234 @chrome_devtools_remote",
8
+ "0000000000000000: 00000002 00000000 00010000 0001 01 61234 @execbro_com.example.app",
9
+ "0000000000000000: 00000002 00000000 00010000 0001 01 71234 /dev/socket/zygote"
10
+ ].join("\n");
11
+ describe("parseExecbroSockets", () => {
12
+ it("returns each SDK package once and ignores other sockets", () => {
13
+ expect(parseExecbroSockets(PROC_NET_UNIX)).toEqual(["com.ihorzheludkov.touchdisabler", "com.example.app"]);
14
+ });
15
+ it("returns nothing for an empty or unreadable listing", () => {
16
+ expect(parseExecbroSockets("")).toEqual([]);
17
+ expect(parseExecbroSockets("cat: /proc/net/unix: Permission denied")).toEqual([]);
18
+ });
19
+ });
20
+ describe("discoverAndConnectNativeApps", () => {
21
+ it("does nothing, and asks nothing of adb, when adb is missing", async () => {
22
+ let discovered = false;
23
+ const lines = await discoverAndConnectNativeApps({
24
+ adbAvailable: async () => false,
25
+ discover: async () => { discovered = true; return []; }
26
+ });
27
+ expect(lines).toEqual([]);
28
+ expect(discovered).toBe(false);
29
+ });
30
+ it("reports connected apps and connection failures per app", async () => {
31
+ const lines = await discoverAndConnectNativeApps({
32
+ adbAvailable: async () => true,
33
+ discover: async () => [
34
+ { serial: "emulator-5554", pkg: "com.good" },
35
+ { serial: "emulator-5554", pkg: "com.bad" }
36
+ ],
37
+ connect: async (_serial, pkg) => {
38
+ if (pkg === "com.bad")
39
+ throw new Error("protocol 2");
40
+ return { deviceInfo: { deviceName: "Good" } };
41
+ }
42
+ });
43
+ expect(lines).toEqual([
44
+ "Android native (emulator-5554): connected to Good (com.good) via execbro-sdk-android",
45
+ "Android native (emulator-5554): found com.bad but could not connect: protocol 2"
46
+ ]);
47
+ });
48
+ });
49
+ describe("connectNativePackage", () => {
50
+ it("reports nothing when no device serves the package, so connect_metro falls through to its port", async () => {
51
+ const lines = await connectNativePackage("github.com", {
52
+ adbAvailable: async () => true,
53
+ serials: async () => ["emulator-5554"],
54
+ connect: async () => { throw new Error("socket hang up"); }
55
+ });
56
+ expect(lines).toEqual([]);
57
+ });
58
+ it("reports the app it connected", async () => {
59
+ const lines = await connectNativePackage("com.good", {
60
+ adbAvailable: async () => true,
61
+ serials: async () => ["emulator-5554"],
62
+ connect: async () => ({ deviceInfo: { deviceName: "Good" } })
63
+ });
64
+ expect(lines).toEqual(["Android native (emulator-5554): connected to Good (com.good) via execbro-sdk-android"]);
65
+ });
66
+ });
67
+ //# sourceMappingURL=nativeDiscovery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeDiscovery.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeDiscovery.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeEnsureConnection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeEnsureConnection.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeEnsureConnection.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, afterEach } from "@jest/globals";
2
+ import WebSocket from "ws";
3
+ import { connectedApps } from "../../core/state.js";
4
+ import { ensureConnection } from "../../core/connection.js";
5
+ import { openNativeSocket } from "../../core/nativeConnection.js";
6
+ import { startFakeSdk, rpcError, HELLO } from "../helpers/fakeNativeSdk.js";
7
+ let fake;
8
+ afterEach(async () => {
9
+ for (const a of connectedApps.values())
10
+ a.ws.terminate();
11
+ connectedApps.clear();
12
+ await fake.close();
13
+ });
14
+ describe("ensureConnection with a native app connected", () => {
15
+ it("leaves the native app alone instead of health-checking it over CDP", async () => {
16
+ fake = await startFakeSdk((method) => {
17
+ if (method === "execbro.hello")
18
+ return HELLO;
19
+ throw rpcError("METHOD_NOT_FOUND", method);
20
+ });
21
+ const app = await openNativeSocket(fake.url, { serial: "emulator-5554", pkg: "com.example.app", port: fake.port });
22
+ await ensureConnection({ port: 1, healthCheck: true });
23
+ expect(connectedApps.get("native:emulator-5554:com.example.app")).toBe(app);
24
+ expect(app.ws.readyState).toBe(WebSocket.OPEN);
25
+ }, 20000);
26
+ });
27
+ //# sourceMappingURL=nativeEnsureConnection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeEnsureConnection.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeEnsureConnection.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAE5E,IAAI,IAA8C,CAAC;AAEnD,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE;QAAE,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;IACzD,aAAa,CAAC,KAAK,EAAE,CAAC;IACtB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAGhF,IAAI,GAAG,MAAM,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;YACjC,IAAI,MAAM,KAAK,eAAe;gBAAE,OAAO,KAAK,CAAC;YAC7C,MAAM,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAGnH,MAAM,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvD,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5E,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,EAAE,KAAK,CAAC,CAAC;AACd,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeGate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeGate.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeGate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,85 @@
1
+ process.env.RN_AI_DEVTOOLS_TEST_MODE = "1";
2
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
3
+ import WebSocket from "ws";
4
+ const { connectedApps } = await import("../../core/state.js");
5
+ const { peekTargetApp, nativeAppFor, getFirstConnectedApp, getConnectedAppByAndroidDeviceId } = await import("../../core/connection.js");
6
+ const { NATIVE_TOOLS, nativeGate } = await import("../../core/nativeCapabilities.js");
7
+ const { toolRegistry } = await import("../../index.js");
8
+ function rnApp(id, deviceName) {
9
+ return {
10
+ ws: { readyState: WebSocket.OPEN },
11
+ deviceInfo: { id, title: deviceName, description: "", appId: "", type: "node", webSocketDebuggerUrl: `ws://x/${id}`, deviceName },
12
+ port: 8081,
13
+ platform: "android"
14
+ };
15
+ }
16
+ function nativeApp(serial, pkg, label) {
17
+ return {
18
+ ...rnApp(`native:${serial}:${pkg}`, label),
19
+ adbSerial: serial,
20
+ runtime: "native",
21
+ nativePackage: pkg
22
+ };
23
+ }
24
+ describe("nativeGate", () => {
25
+ it("lets allowlisted tools through on native", () => {
26
+ for (const t of ["navigate", "list_debug_globals", "inspect_global", "run_app_command", "screenshot", "android_screenshot", "scan_metro"]) {
27
+ expect(nativeGate(t, "native")).toBeNull();
28
+ }
29
+ });
30
+ it("refuses other tools on native, naming the tool and pointing at what works", () => {
31
+ const r = nativeGate("tap", "native");
32
+ expect(r?.isError).toBe(true);
33
+ expect(r?.content[0].text).toContain("tap");
34
+ expect(r?.content[0].text).toContain("screenshot");
35
+ expect(r?._failureKind).toBe("platform_mismatch");
36
+ });
37
+ it("never touches non-native targets", () => {
38
+ expect(nativeGate("tap", undefined)).toBeNull();
39
+ });
40
+ it("allowlists only tools that actually exist", () => {
41
+ const devOnly = new Set(["reset_telemetry"]);
42
+ for (const name of NATIVE_TOOLS)
43
+ if (!devOnly.has(name))
44
+ expect(toolRegistry.has(name)).toBe(true);
45
+ });
46
+ });
47
+ describe("native target resolution", () => {
48
+ beforeEach(() => connectedApps.clear());
49
+ afterEach(() => connectedApps.clear());
50
+ it("never hands a native app to the React Native lookup by adb serial", () => {
51
+ connectedApps.set("n", nativeApp("emulator-5554", "com.example.app", "Example"));
52
+ expect(getConnectedAppByAndroidDeviceId("emulator-5554")).toBeNull();
53
+ expect(getConnectedAppByAndroidDeviceId()).toBeNull();
54
+ const rn = { ...rnApp("rn", "Pixel 9"), adbSerial: "emulator-5554" };
55
+ connectedApps.set("rn", rn);
56
+ expect(getConnectedAppByAndroidDeviceId("emulator-5554")).toBe(rn);
57
+ expect(getConnectedAppByAndroidDeviceId()).toBe(rn);
58
+ });
59
+ it("matches a native app by package, label or serial", () => {
60
+ connectedApps.set("rn", rnApp("rn", "Pixel 9"));
61
+ connectedApps.set("n", nativeApp("emulator-5554", "com.ihorzheludkov.touchdisabler", "TouchDisabler"));
62
+ expect(nativeAppFor("navigate", "com.ihorzheludkov.touchdisabler")?.nativePackage).toBe("com.ihorzheludkov.touchdisabler");
63
+ expect(nativeAppFor("navigate", "TouchDisabler")?.runtime).toBe("native");
64
+ expect(nativeAppFor("navigate", "Pixel 9")).toBeNull();
65
+ });
66
+ it("keeps the same package on two devices apart and fails open when ambiguous", () => {
67
+ connectedApps.set("a", nativeApp("emulator-5554", "com.example.app", "Example"));
68
+ connectedApps.set("b", nativeApp("emulator-5556", "com.example.app", "Example"));
69
+ expect(peekTargetApp("navigate", "emulator-5556")?.adbSerial).toBe("emulator-5556");
70
+ expect(peekTargetApp("navigate", "com.example.app")).toBeUndefined();
71
+ });
72
+ it("prefers a React Native app for a bare call, then a native one when nothing else is open", () => {
73
+ connectedApps.set("n", nativeApp("emulator-5554", "com.example.app", "Example"));
74
+ connectedApps.set("rn", rnApp("rn", "Pixel 9"));
75
+ expect(getFirstConnectedApp()?.runtime).toBeUndefined();
76
+ connectedApps.delete("rn");
77
+ expect(getFirstConnectedApp()?.runtime).toBe("native");
78
+ });
79
+ it("keeps a chromium app ahead of a native one for a bare call, as before native support", () => {
80
+ connectedApps.set("n", nativeApp("emulator-5554", "com.example.app", "Example"));
81
+ connectedApps.set("c", { ...rnApp("c", "FluentTalk"), platform: "chromium", port: 9222 });
82
+ expect(getFirstConnectedApp()?.platform).toBe("chromium");
83
+ });
84
+ });
85
+ //# sourceMappingURL=nativeGate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeGate.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeGate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeGuide.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeGuide.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeGuide.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { getGuideByTopic, getAvailableTopics, DECISION_TREE } from "../../core/guides.js";
3
+ describe("native guide", () => {
4
+ it("is a listed topic covering setup and the four tools", () => {
5
+ expect(getAvailableTopics()).toContain("native");
6
+ const guide = getGuideByTopic("native");
7
+ for (const s of ["execbro-sdk-android", "ExecBro.init", "run_app_command", "inspect_global", "navigate", "list_debug_globals"]) {
8
+ expect(guide).toContain(s);
9
+ }
10
+ });
11
+ it("is announced in the decision tree", () => {
12
+ expect(DECISION_TREE).toContain("native");
13
+ expect(DECISION_TREE).toContain("run_app_command");
14
+ });
15
+ });
16
+ //# sourceMappingURL=nativeGuide.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeGuide.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/nativeGuide.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1F,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAE,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE,CAAC;YAC7H,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,aAAa,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,aAAa,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}