react-native-ai-debugger 1.0.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/LICENSE +21 -0
- package/README.md +303 -0
- package/build/core/android.d.ts +108 -0
- package/build/core/android.d.ts.map +1 -0
- package/build/core/android.js +686 -0
- package/build/core/android.js.map +1 -0
- package/build/core/connection.d.ts +12 -0
- package/build/core/connection.d.ts.map +1 -0
- package/build/core/connection.js +242 -0
- package/build/core/connection.js.map +1 -0
- package/build/core/executor.d.ts +6 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +112 -0
- package/build/core/executor.js.map +1 -0
- package/build/core/index.d.ts +10 -0
- package/build/core/index.d.ts.map +1 -0
- package/build/core/index.js +21 -0
- package/build/core/index.js.map +1 -0
- package/build/core/ios.d.ts +54 -0
- package/build/core/ios.d.ts.map +1 -0
- package/build/core/ios.js +393 -0
- package/build/core/ios.js.map +1 -0
- package/build/core/logs.d.ts +27 -0
- package/build/core/logs.d.ts.map +1 -0
- package/build/core/logs.js +102 -0
- package/build/core/logs.js.map +1 -0
- package/build/core/metro.d.ts +8 -0
- package/build/core/metro.d.ts.map +1 -0
- package/build/core/metro.js +79 -0
- package/build/core/metro.js.map +1 -0
- package/build/core/network.d.ts +37 -0
- package/build/core/network.d.ts.map +1 -0
- package/build/core/network.js +210 -0
- package/build/core/network.js.map +1 -0
- package/build/core/state.d.ts +9 -0
- package/build/core/state.d.ts.map +1 -0
- package/build/core/state.js +16 -0
- package/build/core/state.js.map +1 -0
- package/build/core/types.d.ts +68 -0
- package/build/core/types.d.ts.map +1 -0
- package/build/core/types.js +2 -0
- package/build/core/types.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +951 -0
- package/build/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import sharp from "sharp";
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
// ADB command timeout in milliseconds
|
|
9
|
+
const ADB_TIMEOUT = 30000;
|
|
10
|
+
/**
|
|
11
|
+
* Check if ADB is available in PATH
|
|
12
|
+
*/
|
|
13
|
+
export async function isAdbAvailable() {
|
|
14
|
+
try {
|
|
15
|
+
await execAsync("adb version", { timeout: 5000 });
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* List connected Android devices
|
|
24
|
+
*/
|
|
25
|
+
export async function listAndroidDevices() {
|
|
26
|
+
try {
|
|
27
|
+
const adbAvailable = await isAdbAvailable();
|
|
28
|
+
if (!adbAvailable) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const { stdout } = await execAsync("adb devices -l", { timeout: ADB_TIMEOUT });
|
|
35
|
+
const lines = stdout.trim().split("\n");
|
|
36
|
+
// Skip the "List of devices attached" header
|
|
37
|
+
const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
|
|
38
|
+
if (deviceLines.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
result: "No Android devices connected."
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const devices = deviceLines.map((line) => {
|
|
45
|
+
const parts = line.trim().split(/\s+/);
|
|
46
|
+
const id = parts[0];
|
|
47
|
+
const status = parts[1];
|
|
48
|
+
const device = { id, status };
|
|
49
|
+
// Parse additional info like product:xxx model:xxx device:xxx transport_id:xxx
|
|
50
|
+
for (let i = 2; i < parts.length; i++) {
|
|
51
|
+
const [key, value] = parts[i].split(":");
|
|
52
|
+
if (key === "product")
|
|
53
|
+
device.product = value;
|
|
54
|
+
else if (key === "model")
|
|
55
|
+
device.model = value;
|
|
56
|
+
else if (key === "device")
|
|
57
|
+
device.device = value;
|
|
58
|
+
else if (key === "transport_id")
|
|
59
|
+
device.transportId = value;
|
|
60
|
+
}
|
|
61
|
+
return device;
|
|
62
|
+
});
|
|
63
|
+
const formatted = devices
|
|
64
|
+
.map((d) => {
|
|
65
|
+
let info = `${d.id} (${d.status})`;
|
|
66
|
+
if (d.model)
|
|
67
|
+
info += ` - ${d.model.replace(/_/g, " ")}`;
|
|
68
|
+
if (d.product)
|
|
69
|
+
info += ` [${d.product}]`;
|
|
70
|
+
return info;
|
|
71
|
+
})
|
|
72
|
+
.join("\n");
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
result: `Connected Android devices:\n${formatted}`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `Failed to list devices: ${error instanceof Error ? error.message : String(error)}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the first connected Android device ID
|
|
87
|
+
*/
|
|
88
|
+
export async function getDefaultAndroidDevice() {
|
|
89
|
+
try {
|
|
90
|
+
const { stdout } = await execAsync("adb devices", { timeout: ADB_TIMEOUT });
|
|
91
|
+
const lines = stdout.trim().split("\n");
|
|
92
|
+
const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
|
|
93
|
+
for (const line of deviceLines) {
|
|
94
|
+
const [id, status] = line.trim().split(/\s+/);
|
|
95
|
+
if (status === "device") {
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build device selector for ADB command
|
|
107
|
+
*/
|
|
108
|
+
function buildDeviceArg(deviceId) {
|
|
109
|
+
return deviceId ? `-s ${deviceId}` : "";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Take a screenshot from an Android device
|
|
113
|
+
*/
|
|
114
|
+
export async function androidScreenshot(outputPath, deviceId) {
|
|
115
|
+
try {
|
|
116
|
+
const adbAvailable = await isAdbAvailable();
|
|
117
|
+
if (!adbAvailable) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
124
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
125
|
+
if (!device) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Generate output path if not provided
|
|
132
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
133
|
+
const finalOutputPath = outputPath || path.join(os.tmpdir(), `android-screenshot-${timestamp}.png`);
|
|
134
|
+
// Capture screenshot on device
|
|
135
|
+
const remotePath = "/sdcard/screenshot-temp.png";
|
|
136
|
+
await execAsync(`adb ${deviceArg} shell screencap -p ${remotePath}`, {
|
|
137
|
+
timeout: ADB_TIMEOUT
|
|
138
|
+
});
|
|
139
|
+
// Pull screenshot to local machine
|
|
140
|
+
await execAsync(`adb ${deviceArg} pull ${remotePath} "${finalOutputPath}"`, {
|
|
141
|
+
timeout: ADB_TIMEOUT
|
|
142
|
+
});
|
|
143
|
+
// Clean up remote file
|
|
144
|
+
await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
|
|
145
|
+
timeout: ADB_TIMEOUT
|
|
146
|
+
}).catch(() => {
|
|
147
|
+
// Ignore cleanup errors
|
|
148
|
+
});
|
|
149
|
+
// Resize image if needed (API limit: 2000px max for multi-image requests)
|
|
150
|
+
// Return scale factor so AI can convert image coords to device coords
|
|
151
|
+
const MAX_DIMENSION = 2000;
|
|
152
|
+
const image = sharp(finalOutputPath);
|
|
153
|
+
const metadata = await image.metadata();
|
|
154
|
+
const originalWidth = metadata.width || 0;
|
|
155
|
+
const originalHeight = metadata.height || 0;
|
|
156
|
+
let imageData;
|
|
157
|
+
let scaleFactor = 1;
|
|
158
|
+
if (originalWidth > MAX_DIMENSION || originalHeight > MAX_DIMENSION) {
|
|
159
|
+
// Calculate scale to fit within MAX_DIMENSION
|
|
160
|
+
scaleFactor = Math.max(originalWidth, originalHeight) / MAX_DIMENSION;
|
|
161
|
+
imageData = await image
|
|
162
|
+
.resize(MAX_DIMENSION, MAX_DIMENSION, {
|
|
163
|
+
fit: "inside",
|
|
164
|
+
withoutEnlargement: true
|
|
165
|
+
})
|
|
166
|
+
.png({ compressionLevel: 9 })
|
|
167
|
+
.toBuffer();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
imageData = await image
|
|
171
|
+
.png({ compressionLevel: 9 })
|
|
172
|
+
.toBuffer();
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
result: finalOutputPath,
|
|
177
|
+
data: imageData,
|
|
178
|
+
scaleFactor,
|
|
179
|
+
originalWidth,
|
|
180
|
+
originalHeight
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
error: `Failed to capture screenshot: ${error instanceof Error ? error.message : String(error)}`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Install an APK on an Android device
|
|
192
|
+
*/
|
|
193
|
+
export async function androidInstallApp(apkPath, deviceId, options) {
|
|
194
|
+
try {
|
|
195
|
+
const adbAvailable = await isAdbAvailable();
|
|
196
|
+
if (!adbAvailable) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Verify APK exists
|
|
203
|
+
if (!existsSync(apkPath)) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
error: `APK file not found: ${apkPath}`
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
210
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
211
|
+
if (!device) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Build install flags
|
|
218
|
+
const flags = [];
|
|
219
|
+
if (options?.replace)
|
|
220
|
+
flags.push("-r");
|
|
221
|
+
if (options?.grantPermissions)
|
|
222
|
+
flags.push("-g");
|
|
223
|
+
const flagsStr = flags.length > 0 ? flags.join(" ") + " " : "";
|
|
224
|
+
const { stdout, stderr } = await execAsync(`adb ${deviceArg} install ${flagsStr}"${apkPath}"`, { timeout: 120000 } // 2 minute timeout for install
|
|
225
|
+
);
|
|
226
|
+
const output = stdout + stderr;
|
|
227
|
+
if (output.includes("Success")) {
|
|
228
|
+
return {
|
|
229
|
+
success: true,
|
|
230
|
+
result: `Successfully installed ${path.basename(apkPath)}`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: output.trim() || "Installation failed with unknown error"
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: `Failed to install app: ${error instanceof Error ? error.message : String(error)}`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Launch an app on an Android device
|
|
249
|
+
*/
|
|
250
|
+
export async function androidLaunchApp(packageName, activityName, deviceId) {
|
|
251
|
+
try {
|
|
252
|
+
const adbAvailable = await isAdbAvailable();
|
|
253
|
+
if (!adbAvailable) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
260
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
261
|
+
if (!device) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
let command;
|
|
268
|
+
if (activityName) {
|
|
269
|
+
// Launch specific activity
|
|
270
|
+
command = `adb ${deviceArg} shell am start -n ${packageName}/${activityName}`;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// Launch main/launcher activity
|
|
274
|
+
command = `adb ${deviceArg} shell monkey -p ${packageName} -c android.intent.category.LAUNCHER 1`;
|
|
275
|
+
}
|
|
276
|
+
const { stdout, stderr } = await execAsync(command, { timeout: ADB_TIMEOUT });
|
|
277
|
+
const output = stdout + stderr;
|
|
278
|
+
// Check for errors
|
|
279
|
+
if (output.includes("Error") || output.includes("Exception")) {
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: output.trim()
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
result: `Launched ${packageName}${activityName ? `/${activityName}` : ""}`
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
error: `Failed to launch app: ${error instanceof Error ? error.message : String(error)}`
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get list of installed packages on the device
|
|
299
|
+
*/
|
|
300
|
+
export async function androidListPackages(deviceId, filter) {
|
|
301
|
+
try {
|
|
302
|
+
const adbAvailable = await isAdbAvailable();
|
|
303
|
+
if (!adbAvailable) {
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
310
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
311
|
+
if (!device) {
|
|
312
|
+
return {
|
|
313
|
+
success: false,
|
|
314
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const { stdout } = await execAsync(`adb ${deviceArg} shell pm list packages`, {
|
|
318
|
+
timeout: ADB_TIMEOUT
|
|
319
|
+
});
|
|
320
|
+
let packages = stdout
|
|
321
|
+
.trim()
|
|
322
|
+
.split("\n")
|
|
323
|
+
.map((line) => line.replace("package:", "").trim())
|
|
324
|
+
.filter((pkg) => pkg.length > 0);
|
|
325
|
+
if (filter) {
|
|
326
|
+
const filterLower = filter.toLowerCase();
|
|
327
|
+
packages = packages.filter((pkg) => pkg.toLowerCase().includes(filterLower));
|
|
328
|
+
}
|
|
329
|
+
if (packages.length === 0) {
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
result: filter ? `No packages found matching "${filter}"` : "No packages found"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
success: true,
|
|
337
|
+
result: `Installed packages${filter ? ` matching "${filter}"` : ""}:\n${packages.join("\n")}`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
success: false,
|
|
343
|
+
error: `Failed to list packages: ${error instanceof Error ? error.message : String(error)}`
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// UI Input Functions (Phase 2)
|
|
349
|
+
// ============================================================================
|
|
350
|
+
/**
|
|
351
|
+
* Common key event codes for Android
|
|
352
|
+
*/
|
|
353
|
+
export const ANDROID_KEY_EVENTS = {
|
|
354
|
+
HOME: 3,
|
|
355
|
+
BACK: 4,
|
|
356
|
+
CALL: 5,
|
|
357
|
+
END_CALL: 6,
|
|
358
|
+
VOLUME_UP: 24,
|
|
359
|
+
VOLUME_DOWN: 25,
|
|
360
|
+
POWER: 26,
|
|
361
|
+
CAMERA: 27,
|
|
362
|
+
CLEAR: 28,
|
|
363
|
+
TAB: 61,
|
|
364
|
+
ENTER: 66,
|
|
365
|
+
DEL: 67,
|
|
366
|
+
MENU: 82,
|
|
367
|
+
SEARCH: 84,
|
|
368
|
+
MEDIA_PLAY_PAUSE: 85,
|
|
369
|
+
MEDIA_STOP: 86,
|
|
370
|
+
MEDIA_NEXT: 87,
|
|
371
|
+
MEDIA_PREVIOUS: 88,
|
|
372
|
+
MOVE_HOME: 122,
|
|
373
|
+
MOVE_END: 123,
|
|
374
|
+
APP_SWITCH: 187,
|
|
375
|
+
ESCAPE: 111
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* Tap at coordinates on an Android device
|
|
379
|
+
*/
|
|
380
|
+
export async function androidTap(x, y, deviceId) {
|
|
381
|
+
try {
|
|
382
|
+
const adbAvailable = await isAdbAvailable();
|
|
383
|
+
if (!adbAvailable) {
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
390
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
391
|
+
if (!device) {
|
|
392
|
+
return {
|
|
393
|
+
success: false,
|
|
394
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
await execAsync(`adb ${deviceArg} shell input tap ${Math.round(x)} ${Math.round(y)}`, {
|
|
398
|
+
timeout: ADB_TIMEOUT
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
success: true,
|
|
402
|
+
result: `Tapped at (${Math.round(x)}, ${Math.round(y)})`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
return {
|
|
407
|
+
success: false,
|
|
408
|
+
error: `Failed to tap: ${error instanceof Error ? error.message : String(error)}`
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Long press at coordinates on an Android device
|
|
414
|
+
*/
|
|
415
|
+
export async function androidLongPress(x, y, durationMs = 1000, deviceId) {
|
|
416
|
+
try {
|
|
417
|
+
const adbAvailable = await isAdbAvailable();
|
|
418
|
+
if (!adbAvailable) {
|
|
419
|
+
return {
|
|
420
|
+
success: false,
|
|
421
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
425
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
426
|
+
if (!device) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
// Long press is implemented as a swipe from the same point to the same point
|
|
433
|
+
const xRounded = Math.round(x);
|
|
434
|
+
const yRounded = Math.round(y);
|
|
435
|
+
await execAsync(`adb ${deviceArg} shell input swipe ${xRounded} ${yRounded} ${xRounded} ${yRounded} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
|
|
436
|
+
return {
|
|
437
|
+
success: true,
|
|
438
|
+
result: `Long pressed at (${xRounded}, ${yRounded}) for ${durationMs}ms`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return {
|
|
443
|
+
success: false,
|
|
444
|
+
error: `Failed to long press: ${error instanceof Error ? error.message : String(error)}`
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Swipe on an Android device
|
|
450
|
+
*/
|
|
451
|
+
export async function androidSwipe(startX, startY, endX, endY, durationMs = 300, deviceId) {
|
|
452
|
+
try {
|
|
453
|
+
const adbAvailable = await isAdbAvailable();
|
|
454
|
+
if (!adbAvailable) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
461
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
462
|
+
if (!device) {
|
|
463
|
+
return {
|
|
464
|
+
success: false,
|
|
465
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const x1 = Math.round(startX);
|
|
469
|
+
const y1 = Math.round(startY);
|
|
470
|
+
const x2 = Math.round(endX);
|
|
471
|
+
const y2 = Math.round(endY);
|
|
472
|
+
await execAsync(`adb ${deviceArg} shell input swipe ${x1} ${y1} ${x2} ${y2} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
|
|
473
|
+
return {
|
|
474
|
+
success: true,
|
|
475
|
+
result: `Swiped from (${x1}, ${y1}) to (${x2}, ${y2}) in ${durationMs}ms`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
return {
|
|
480
|
+
success: false,
|
|
481
|
+
error: `Failed to swipe: ${error instanceof Error ? error.message : String(error)}`
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Input text on an Android device
|
|
487
|
+
*
|
|
488
|
+
* ADB input text has limitations with special characters.
|
|
489
|
+
* This function handles escaping properly for URLs, emails, and special strings.
|
|
490
|
+
*/
|
|
491
|
+
export async function androidInputText(text, deviceId) {
|
|
492
|
+
try {
|
|
493
|
+
const adbAvailable = await isAdbAvailable();
|
|
494
|
+
if (!adbAvailable) {
|
|
495
|
+
return {
|
|
496
|
+
success: false,
|
|
497
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
501
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
502
|
+
if (!device) {
|
|
503
|
+
return {
|
|
504
|
+
success: false,
|
|
505
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
// For complex strings with special characters, type character by character
|
|
509
|
+
// using key events for reliability
|
|
510
|
+
const hasComplexChars = /[/:?=&#@%+]/.test(text);
|
|
511
|
+
if (hasComplexChars) {
|
|
512
|
+
// Use character-by-character input for strings with special chars
|
|
513
|
+
// This is slower but more reliable for URLs, emails, etc.
|
|
514
|
+
for (const char of text) {
|
|
515
|
+
let keyCmd;
|
|
516
|
+
// Map special characters to their escaped form or use direct input
|
|
517
|
+
switch (char) {
|
|
518
|
+
case " ":
|
|
519
|
+
keyCmd = `adb ${deviceArg} shell input text "%s"`;
|
|
520
|
+
break;
|
|
521
|
+
case "'":
|
|
522
|
+
// Single quote needs special handling
|
|
523
|
+
keyCmd = `adb ${deviceArg} shell input text "\\'"`;
|
|
524
|
+
break;
|
|
525
|
+
case '"':
|
|
526
|
+
keyCmd = `adb ${deviceArg} shell input text '\\"'`;
|
|
527
|
+
break;
|
|
528
|
+
case "\\":
|
|
529
|
+
keyCmd = `adb ${deviceArg} shell input text "\\\\"`;
|
|
530
|
+
break;
|
|
531
|
+
case "&":
|
|
532
|
+
keyCmd = `adb ${deviceArg} shell input text "\\&"`;
|
|
533
|
+
break;
|
|
534
|
+
case "|":
|
|
535
|
+
keyCmd = `adb ${deviceArg} shell input text "\\|"`;
|
|
536
|
+
break;
|
|
537
|
+
case ";":
|
|
538
|
+
keyCmd = `adb ${deviceArg} shell input text "\\;"`;
|
|
539
|
+
break;
|
|
540
|
+
case "<":
|
|
541
|
+
keyCmd = `adb ${deviceArg} shell input text "\\<"`;
|
|
542
|
+
break;
|
|
543
|
+
case ">":
|
|
544
|
+
keyCmd = `adb ${deviceArg} shell input text "\\>"`;
|
|
545
|
+
break;
|
|
546
|
+
case "(":
|
|
547
|
+
keyCmd = `adb ${deviceArg} shell input text "\\("`;
|
|
548
|
+
break;
|
|
549
|
+
case ")":
|
|
550
|
+
keyCmd = `adb ${deviceArg} shell input text "\\)"`;
|
|
551
|
+
break;
|
|
552
|
+
case "$":
|
|
553
|
+
keyCmd = `adb ${deviceArg} shell input text "\\$"`;
|
|
554
|
+
break;
|
|
555
|
+
case "`":
|
|
556
|
+
keyCmd = `adb ${deviceArg} shell input text "\\\`"`;
|
|
557
|
+
break;
|
|
558
|
+
default:
|
|
559
|
+
// For most characters, wrap in single quotes to prevent shell interpretation
|
|
560
|
+
// Single quotes preserve literal meaning of all characters except single quote itself
|
|
561
|
+
keyCmd = `adb ${deviceArg} shell input text '${char}'`;
|
|
562
|
+
}
|
|
563
|
+
await execAsync(keyCmd, { timeout: 5000 });
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
success: true,
|
|
567
|
+
result: `Typed: "${text}"`
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
// For simple alphanumeric strings, use the faster bulk input
|
|
571
|
+
// Escape basic special characters
|
|
572
|
+
const escapedText = text
|
|
573
|
+
.replace(/\\/g, "\\\\")
|
|
574
|
+
.replace(/"/g, '\\"')
|
|
575
|
+
.replace(/'/g, "\\'")
|
|
576
|
+
.replace(/`/g, "\\`")
|
|
577
|
+
.replace(/\$/g, "\\$")
|
|
578
|
+
.replace(/ /g, "%s");
|
|
579
|
+
await execAsync(`adb ${deviceArg} shell input text "${escapedText}"`, {
|
|
580
|
+
timeout: ADB_TIMEOUT
|
|
581
|
+
});
|
|
582
|
+
return {
|
|
583
|
+
success: true,
|
|
584
|
+
result: `Typed: "${text}"`
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
return {
|
|
589
|
+
success: false,
|
|
590
|
+
error: `Failed to input text: ${error instanceof Error ? error.message : String(error)}`
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Send a key event to an Android device
|
|
596
|
+
*/
|
|
597
|
+
export async function androidKeyEvent(keyCode, deviceId) {
|
|
598
|
+
try {
|
|
599
|
+
const adbAvailable = await isAdbAvailable();
|
|
600
|
+
if (!adbAvailable) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
607
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
608
|
+
if (!device) {
|
|
609
|
+
return {
|
|
610
|
+
success: false,
|
|
611
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
// Resolve key code from name if needed
|
|
615
|
+
const resolvedKeyCode = typeof keyCode === "string" ? ANDROID_KEY_EVENTS[keyCode] : keyCode;
|
|
616
|
+
if (resolvedKeyCode === undefined) {
|
|
617
|
+
return {
|
|
618
|
+
success: false,
|
|
619
|
+
error: `Invalid key code: ${keyCode}`
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
await execAsync(`adb ${deviceArg} shell input keyevent ${resolvedKeyCode}`, {
|
|
623
|
+
timeout: ADB_TIMEOUT
|
|
624
|
+
});
|
|
625
|
+
// Get key name for display
|
|
626
|
+
const keyName = typeof keyCode === "string"
|
|
627
|
+
? keyCode
|
|
628
|
+
: Object.entries(ANDROID_KEY_EVENTS).find(([_, v]) => v === keyCode)?.[0] ||
|
|
629
|
+
`keycode ${keyCode}`;
|
|
630
|
+
return {
|
|
631
|
+
success: true,
|
|
632
|
+
result: `Sent key event: ${keyName}`
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
return {
|
|
637
|
+
success: false,
|
|
638
|
+
error: `Failed to send key event: ${error instanceof Error ? error.message : String(error)}`
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Get device screen size
|
|
644
|
+
*/
|
|
645
|
+
export async function androidGetScreenSize(deviceId) {
|
|
646
|
+
try {
|
|
647
|
+
const adbAvailable = await isAdbAvailable();
|
|
648
|
+
if (!adbAvailable) {
|
|
649
|
+
return {
|
|
650
|
+
success: false,
|
|
651
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
655
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
656
|
+
if (!device) {
|
|
657
|
+
return {
|
|
658
|
+
success: false,
|
|
659
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
const { stdout } = await execAsync(`adb ${deviceArg} shell wm size`, {
|
|
663
|
+
timeout: ADB_TIMEOUT
|
|
664
|
+
});
|
|
665
|
+
// Parse output like "Physical size: 1080x1920"
|
|
666
|
+
const match = stdout.match(/(\d+)x(\d+)/);
|
|
667
|
+
if (match) {
|
|
668
|
+
return {
|
|
669
|
+
success: true,
|
|
670
|
+
width: parseInt(match[1], 10),
|
|
671
|
+
height: parseInt(match[2], 10)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
success: false,
|
|
676
|
+
error: `Could not parse screen size from: ${stdout.trim()}`
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
return {
|
|
681
|
+
success: false,
|
|
682
|
+
error: `Failed to get screen size: ${error instanceof Error ? error.message : String(error)}`
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
//# sourceMappingURL=android.js.map
|