1import {
2  AndroidConfig,
3  AndroidManifest,
4  ConfigPlugin,
5  withAndroidManifest,
6} from '@expo/config-plugins';
7import {
8  ManifestActivity,
9  ManifestIntentFilter,
10} from '@expo/config-plugins/build/android/Manifest';
11import { ExpoConfig } from 'expo/config';
12
13import getDefaultScheme from './getDefaultScheme';
14
15export const withGeneratedAndroidScheme: ConfigPlugin = (config) => {
16  return withAndroidManifest(config, (config) => {
17    config.modResults = setGeneratedAndroidScheme(config, config.modResults);
18    config.modResults = removeExpoSchemaFromVerifiedIntentFilters(config, config.modResults);
19    return config;
20  });
21};
22
23export function setGeneratedAndroidScheme(
24  config: Pick<ExpoConfig, 'scheme' | 'slug'>,
25  androidManifest: AndroidManifest
26): AndroidManifest {
27  // Generate a cross-platform scheme used to launch the dev client.
28  const scheme = getDefaultScheme(config);
29  if (!AndroidConfig.Scheme.hasScheme(scheme, androidManifest)) {
30    androidManifest = AndroidConfig.Scheme.appendScheme(scheme, androidManifest);
31  }
32
33  return androidManifest;
34}
35
36/**
37 * Remove the custom Expo dev client scheme from intent filters, which are set to `autoVerify=true`.
38 * The custom scheme `<data android:scheme="exp+<slug>"/>` seems to block verification for these intent filters.
39 * This plugin makes sure there is no scheme in the autoVerify intent filters, that starts with `exp+`.
40
41 * Iterate over all `autoVerify=true` intent filters, and pull out schemes matching with `exp+<slug>`.
42 *
43 * @param {AndroidManifest} androidManifest
44 */
45export function removeExpoSchemaFromVerifiedIntentFilters(
46  config: Pick<ExpoConfig, 'scheme' | 'slug'>,
47  androidManifest: AndroidManifest
48) {
49  // Generate a cross-platform scheme used to launch the dev client.
50  const defaultScheme = getDefaultScheme(config);
51  // see: https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L164-L179
52  for (const application of androidManifest.manifest.application || []) {
53    for (const activity of application.activity || []) {
54      if (activityHasSingleTaskLaunchMode(activity)) {
55        for (const intentFilter of activity['intent-filter'] || []) {
56          if (intentFilterHasAutoVerification(intentFilter) && intentFilter?.data) {
57            intentFilter.data = intentFilterRemoveSchemeFromData(
58              intentFilter,
59              (scheme: string) => scheme === defaultScheme
60            );
61          }
62        }
63        break;
64      }
65    }
66  }
67
68  return androidManifest;
69}
70
71/**
72 * Determine if the activity should contain the intent filters to clean.
73 *
74 * @see https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L166
75 */
76function activityHasSingleTaskLaunchMode(activity: ManifestActivity) {
77  return activity?.$?.['android:launchMode'] === 'singleTask';
78}
79
80/**
81 * Determine if the intent filter has `autoVerify=true`.
82 */
83function intentFilterHasAutoVerification(intentFilter: ManifestIntentFilter) {
84  return intentFilter?.$?.['android:autoVerify'] === 'true';
85}
86
87/**
88 * Remove schemes from the intent filter that matches the function.
89 */
90function intentFilterRemoveSchemeFromData(intentFilter: ManifestIntentFilter, schemeMatcher: any) {
91  return intentFilter?.data?.filter((entry) => !schemeMatcher(entry?.$['android:scheme'] || ''));
92}
93