async function selectDevicesAndClient()

in desktop/flipper-ui-core/src/dispatcher/handleOpenPluginDeeplink.tsx [451:557]


async function selectDevicesAndClient(
  store: Store,
  params: OpenPluginParams,
  title: string,
  isDevicePlugin: boolean,
): Promise<DeeplinkError | BaseDevice | Client> {
  function findValidDevices() {
    // find connected devices with the right OS.
    return (
      store
        .getState()
        .connections.devices.filter((d) => d.connected.get())
        .filter(
          (d) => params.devices.length === 0 || params.devices.includes(d.os),
        )
        // This filters out OS-level devices which are causing more confusion than good
        // when used with deeplinks.
        .filter(canBeDefaultDevice)
    );
  }

  // loop until we have devices (or abort)
  while (!findValidDevices().length) {
    if (!(await launchDeviceDialog(store, params, title))) {
      return {errorState: 'PLUGIN_DEVICE_BAIL'};
    }
  }

  // at this point we have 1 or more valid devices
  const availableDevices = findValidDevices();
  console.debug(
    '[deeplink] selectDevicesAndClient found at least one more valid device:',
    availableDevices.map((d) => d.description),
  );
  // device plugin
  if (isDevicePlugin) {
    if (availableDevices.length === 1) {
      return availableDevices[0];
    }
    const selectedDevice = await selectDeviceDialog(availableDevices, title);
    if (!selectedDevice) {
      return {errorState: 'PLUGIN_DEVICE_SELECTION_BAIL'};
    }
    return selectedDevice;
  }

  console.debug('[deeplink] Not a device plugin. Waiting for valid client.');
  // wait for valid client
  while (true) {
    const origClients = store.getState().connections.clients;
    const validClients = getAllClients(store.getState().connections)
      .filter(
        // correct app name, or, if not set, an app that at least supports this plugin
        (c) =>
          params.client
            ? c.query.app === params.client
            : c.plugins.has(params.pluginId),
      )
      .filter((c) => c.connected.get())
      .filter((c) => availableDevices.includes(c.device));

    if (validClients.length === 1) {
      return validClients[0];
    }
    if (validClients.length > 1) {
      const selectedClient = await selectClientDialog(validClients, title);
      if (!selectedClient) {
        return {errorState: 'PLUGIN_CLIENT_SELECTION_BAIL'};
      }
      return selectedClient;
    }

    // no valid client yet
    const result = await new Promise<boolean>((resolve) => {
      const dialog = Dialog.alert({
        title,
        type: 'warning',
        message: params.client
          ? `Application '${params.client}' doesn't seem to be connected yet. Please start a debug version of the app to continue.`
          : `No application that supports plugin '${params.pluginId}' seems to be running. Please start a debug application that supports the plugin to continue.`,
        okText: 'Cancel',
      });
      // eslint-disable-next-line promise/catch-or-return
      dialog.then(() => resolve(false));

      // eslint-disable-next-line promise/catch-or-return
      waitFor(store, (state) => state.connections.clients !== origClients).then(
        () => {
          dialog.close();
          resolve(true);
        },
      );

      // We also want to react to changes in the available plugins and refresh.
      origClients.forEach((c) =>
        c.on('plugins-change', () => {
          dialog.close();
          resolve(true);
        }),
      );
    });

    if (!result) {
      return {errorState: 'PLUGIN_CLIENT_BAIL'}; // User cancelled
    }
  }
}