1import { assertSdkRoot } from './AndroidSdk';
2import { adbArgs, Device, getAttachedDevicesAsync, getServer, logUnauthorized } from './adb';
3import * as Log from '../../../log';
4import { installExitHooks } from '../../../utils/exit';
5
6const debug = require('debug')('expo:start:platforms:android:adbReverse') as typeof console.log;
7
8let removeExitHook: (() => void) | null = null;
9
10export function hasAdbReverseAsync(): boolean {
11  try {
12    return !!assertSdkRoot();
13  } catch (error: any) {
14    debug('Failed to resolve the Android SDK path, skipping ADB: %s', error.message);
15    return false;
16  }
17}
18
19export async function startAdbReverseAsync(ports: number[]): Promise<boolean> {
20  // Install cleanup automatically...
21  removeExitHook = installExitHooks(() => {
22    stopAdbReverseAsync(ports);
23  });
24
25  const devices = await getAttachedDevicesAsync();
26  for (const device of devices) {
27    for (const port of ports) {
28      if (!(await adbReverseAsync(device, port))) {
29        debug(`Failed to start reverse port ${port} on device "${device.name}"`);
30        return false;
31      }
32    }
33  }
34  return true;
35}
36
37export async function stopAdbReverseAsync(ports: number[]): Promise<void> {
38  removeExitHook?.();
39
40  const devices = await getAttachedDevicesAsync();
41  for (const device of devices) {
42    for (const port of ports) {
43      await adbReverseRemoveAsync(device, port);
44    }
45  }
46}
47
48async function adbReverseAsync(device: Device, port: number): Promise<boolean> {
49  if (!device.isAuthorized) {
50    logUnauthorized(device);
51    return false;
52  }
53
54  try {
55    await getServer().runAsync(adbArgs(device.pid, 'reverse', `tcp:${port}`, `tcp:${port}`));
56    return true;
57  } catch (error: any) {
58    Log.warn(`[ADB] Couldn't reverse port ${port}: ${error.message}`);
59    return false;
60  }
61}
62
63async function adbReverseRemoveAsync(device: Device, port: number): Promise<boolean> {
64  if (!device.isAuthorized) {
65    return false;
66  }
67
68  try {
69    await getServer().runAsync(adbArgs(device.pid, 'reverse', '--remove', `tcp:${port}`));
70    return true;
71  } catch (error: any) {
72    // Don't send this to warn because we call this preemptively sometimes
73    debug(`Could not unforward port ${port}: ${error.message}`);
74    return false;
75  }
76}
77