expo: Some schemes might break Android verified app links
Summary
One of our partners reported issues in verifying app links on Android. They added the expo.android.intentFilters
, with the autoVerify
property set to true. We found out that the added scheme by the dev client (<data android:scheme="exp+<slug>" />
), was also added to these intent filters. That scheme broke the app link verification process on Android.
Unfortunately, starting from Android 12, verifying your app links seems required to even be listed in the “Open with app” list.
Looking at the dev client’s config plugin:
// Generate a cross-platform scheme used to launch the dev client.
const scheme = getDefaultScheme(config);
if (!AndroidConfig.Scheme.hasScheme(scheme, androidManifest)) {
androidManifest = AndroidConfig.Scheme.appendScheme(scheme, androidManifest);
}
return androidManifest;
There is no great way for plugins to not add a scheme to custom intent filters, or to remove them specifically from the autoVerify
-ed intent filters.
AndroidManifest.xml activity examples
Example activity intent filters in AndroidManifest.xml that breaks
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="<slug>"/>
<data android:scheme="<package>"/>
<data android:scheme="exp+<slug>"/>
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<data android:scheme="exp+<slug>"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Example activity intent filters in AndroidManifest.xml that works
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="<slug>"/>
<data android:scheme="<package>"/>
<data android:scheme="exp+<slug>"/>
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<data android:scheme="https" android:host="<name>.onelink.me" android:pathPrefix="/XXXX"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Workaround plugin that pulls the exp+
schemes from auto verifying intent filters
withAndroidVerifiedLinksWorkaround.js
const { createRunOncePlugin, withAndroidManifest } = require('@expo/config-plugins');
/**
* @typedef {import('@expo/config-plugins').ConfigPlugin} ConfigPlugin
* @typedef {import('@expo/config-plugins').AndroidManifest} AndroidManifest
*/
/**
* Remove the custom Expo dev client scheme from intent filters, which are set to `autoVerify=true`.
* The custom scheme `<data android:scheme="exp+<slug>"/>` seems to block verification for these intent filters.
* This plugin makes sure there is no scheme in the autoVerify intent filters, that starts with `exp+`.
*
* @type {ConfigPlugin}
*/
const withAndroidVerfiedLinksWorkaround = (config) => (
withAndroidManifest(config, (config) => {
config.modResults = removeExpoSchemaFromVerifiedIntentFilters(config.modResults);
return config;
})
);
/**
* Iterate over all `autoVerify=true` intent filters, and pull out schemes starting with `exp+`.
*
* @param {AndroidManifest} androidManifest
*/
function removeExpoSchemaFromVerifiedIntentFilters(androidManifest) {
// see: https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L164-L179
for (const application of androidManifest.manifest.application || []) {
for (const activity of application.activity || []) {
if (activityHasSingleTaskLaunchMode(activity)) {
for (const intentFilter of activity['intent-filter'] || []) {
if (intentFilterHasAutoVerification(intentFilter) && intentFilter?.data) {
intentFilter.data = intentFilterRemoveSchemeFromData(intentFilter, (scheme) => scheme?.startsWith('exp+'));
}
}
break;
}
}
}
return androidManifest;
}
/**
* Determine if the activity should contain the intent filters to clean.
*
* @see https://github.com/expo/expo-cli/blob/f1624c75b52cc1c4f99354ec4021494e0eff74aa/packages/config-plugins/src/android/Scheme.ts#L166
*/
function activityHasSingleTaskLaunchMode(activity) {
return activity?.$?.['android:launchMode'] === 'singleTask';
}
/**
* Determine if the intent filter has `autoVerify=true`.
*/
function intentFilterHasAutoVerification(intentFilter) {
return intentFilter?.$?.['android:autoVerify'] === 'true';
}
/**
* Remove schemes from the intent filter that matches the function.
*/
function intentFilterRemoveSchemeFromData(intentFilter, schemeMatcher) {
return intentFilter?.data?.filter(entry => !schemeMatcher(entry?.$['android:scheme'] || ''));
}
module.exports = createRunOncePlugin(withAndroidVerfiedLinksWorkaround, 'withAndroidVerfiedLinksWorkaround', '1.0.0');
Environment
Expo CLI 5.0.5 environment info:
System:
OS: Windows 10 10.0.22000
Binaries:
Node: 16.13.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.4 - C:\Program Files\nodejs\yarn.CMD
npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD
IDEs:
Android Studio: Version 2020.3.0.0 AI-203.7717.56.2031.7784292
npmPackages:
expo: ~44.0.0 => 44.0.6
react: 17.0.1 => 17.0.1
react-dom: 17.0.1 => 17.0.1
react-native: 0.64.3 => 0.64.3
react-native-web: 0.17.1 => 0.17.1
Expo Workflow: managed
Please specify your device/emulator/simulator platform, model and version
Android 12
Error output
none
Reproducible demo or steps to reproduce from a blank project
Verifying app links is a delicate process, you can find some debugging options here: https://developer.android.com/training/app-links/verify-site-associations#check-link-policies
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 21
- Comments: 15 (6 by maintainers)
@mguyard Create a new file for the plugin e.g.
plugins/withAndroidVerifiedLinksWorkaround.js
. Then add the plugin to yourapp.json
underexpo.plugins
e.g.Yes, works with
eas build
. You have to create a new build for the changes to take effect.We are facing this issue currently with RN-Firebase and dynamic links, the same setup works fine in iOS and pre Android 12 builds. We have tried the config plugin route without success yet. Would be good to have an update if this issue is being worked on and or of it was tied to the Expo dev client or other libraries.
Same issue happens with the expo plugin for
react-native-fbsdk-next
. It appends this to the intent filters which breaks autoVerify in the same way, e.g:See: https://github.com/thebergamo/react-native-fbsdk-next/issues/329
Since I’m dubious about other plugins breaking this, I modified @byCedric’s solution to be a general purpose validator. For app links which use auto verification, it will only ever include entries which use a
http
orhttps
scheme. As far as I can tell this is in alignment with the Android docs, since auto verification applies to website links only.withAndroidVerifiedLinksWorkaround.js
Hello,
I’ve exactly the same issue. I see that this bug isn’t already assigned. Is it plan to resolve this bug in a new version ?
As i’m pretty new expo user, i don’t understand everything and how to use the workaround purpose. I someone can help me to understand how to apply the workaround (where, how, etc…) this will be very appreciate.
Another question, does this workaround works if i use “eas build” ?
Thanks by advance
@byCedric Thank you for writing up this issue and sharing your workaround!
I can confirm that we ran into the same problem and the above workaround resolved it for us.