1f1bb9346SEvan Baconimport {
2f1bb9346SEvan Bacon  AndroidConfig,
3f1bb9346SEvan Bacon  AndroidManifest,
4f1bb9346SEvan Bacon  ConfigPlugin,
5f1bb9346SEvan Bacon  withAndroidManifest,
6f1bb9346SEvan Bacon} from '@expo/config-plugins';
78f4283e8Sandyimport {
88f4283e8Sandy  ManifestActivity,
98f4283e8Sandy  ManifestIntentFilter,
108f4283e8Sandy} from '@expo/config-plugins/build/android/Manifest';
11*435bbba8SBrent Vatneimport { ExpoConfig } from 'expo/config';
12dd3fc24cSVille Immonen
134813bc2eSVille Immonenimport getDefaultScheme from './getDefaultScheme';
14dd3fc24cSVille Immonen
15f1bb9346SEvan Baconexport const withGeneratedAndroidScheme: ConfigPlugin = (config) => {
16f1bb9346SEvan Bacon  return withAndroidManifest(config, (config) => {
17f1bb9346SEvan Bacon    config.modResults = setGeneratedAndroidScheme(config, config.modResults);
188f4283e8Sandy    config.modResults = removeExpoSchemaFromVerifiedIntentFilters(config, config.modResults);
19f1bb9346SEvan Bacon    return config;
20f1bb9346SEvan Bacon  });
21f1bb9346SEvan Bacon};
22dd3fc24cSVille Immonen
23dd3fc24cSVille Immonenexport function setGeneratedAndroidScheme(
24dd3fc24cSVille Immonen  config: Pick<ExpoConfig, 'scheme' | 'slug'>,
25dd3fc24cSVille Immonen  androidManifest: AndroidManifest
26dd3fc24cSVille Immonen): AndroidManifest {
27dd3fc24cSVille Immonen  // Generate a cross-platform scheme used to launch the dev client.
284813bc2eSVille Immonen  const scheme = getDefaultScheme(config);
29f1bb9346SEvan Bacon  if (!AndroidConfig.Scheme.hasScheme(scheme, androidManifest)) {
30f1bb9346SEvan Bacon    androidManifest = AndroidConfig.Scheme.appendScheme(scheme, androidManifest);
31f1bb9346SEvan Bacon  }
328f4283e8Sandy
33f1bb9346SEvan Bacon  return androidManifest;
34dd3fc24cSVille Immonen}
358f4283e8Sandy
368f4283e8Sandy/**
378f4283e8Sandy * Remove the custom Expo dev client scheme from intent filters, which are set to `autoVerify=true`.
388f4283e8Sandy * The custom scheme `<data android:scheme="exp+<slug>"/>` seems to block verification for these intent filters.
398f4283e8Sandy * This plugin makes sure there is no scheme in the autoVerify intent filters, that starts with `exp+`.
408f4283e8Sandy
418f4283e8Sandy * Iterate over all `autoVerify=true` intent filters, and pull out schemes matching with `exp+<slug>`.
428f4283e8Sandy *
438f4283e8Sandy * @param {AndroidManifest} androidManifest
448f4283e8Sandy */
458f4283e8Sandyexport function removeExpoSchemaFromVerifiedIntentFilters(
468f4283e8Sandy  config: Pick<ExpoConfig, 'scheme' | 'slug'>,
478f4283e8Sandy  androidManifest: AndroidManifest
488f4283e8Sandy) {
498f4283e8Sandy  // Generate a cross-platform scheme used to launch the dev client.
508f4283e8Sandy  const defaultScheme = getDefaultScheme(config);
518f4283e8Sandy  // see: https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L164-L179
528f4283e8Sandy  for (const application of androidManifest.manifest.application || []) {
538f4283e8Sandy    for (const activity of application.activity || []) {
548f4283e8Sandy      if (activityHasSingleTaskLaunchMode(activity)) {
558f4283e8Sandy        for (const intentFilter of activity['intent-filter'] || []) {
568f4283e8Sandy          if (intentFilterHasAutoVerification(intentFilter) && intentFilter?.data) {
578f4283e8Sandy            intentFilter.data = intentFilterRemoveSchemeFromData(
588f4283e8Sandy              intentFilter,
598f4283e8Sandy              (scheme: string) => scheme === defaultScheme
608f4283e8Sandy            );
618f4283e8Sandy          }
628f4283e8Sandy        }
638f4283e8Sandy        break;
648f4283e8Sandy      }
658f4283e8Sandy    }
668f4283e8Sandy  }
678f4283e8Sandy
688f4283e8Sandy  return androidManifest;
698f4283e8Sandy}
708f4283e8Sandy
718f4283e8Sandy/**
728f4283e8Sandy * Determine if the activity should contain the intent filters to clean.
738f4283e8Sandy *
748f4283e8Sandy * @see https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L166
758f4283e8Sandy */
768f4283e8Sandyfunction activityHasSingleTaskLaunchMode(activity: ManifestActivity) {
778f4283e8Sandy  return activity?.$?.['android:launchMode'] === 'singleTask';
788f4283e8Sandy}
798f4283e8Sandy
808f4283e8Sandy/**
818f4283e8Sandy * Determine if the intent filter has `autoVerify=true`.
828f4283e8Sandy */
838f4283e8Sandyfunction intentFilterHasAutoVerification(intentFilter: ManifestIntentFilter) {
848f4283e8Sandy  return intentFilter?.$?.['android:autoVerify'] === 'true';
858f4283e8Sandy}
868f4283e8Sandy
878f4283e8Sandy/**
888f4283e8Sandy * Remove schemes from the intent filter that matches the function.
898f4283e8Sandy */
908f4283e8Sandyfunction intentFilterRemoveSchemeFromData(intentFilter: ManifestIntentFilter, schemeMatcher: any) {
918f4283e8Sandy  return intentFilter?.data?.filter((entry) => !schemeMatcher(entry?.$['android:scheme'] || ''));
928f4283e8Sandy}
93