OTA iOS: Set the value of updates.enabled in EXShell.plist

In the doc here (https://docs.expo.io/versions/latest/workflow/configuration/#updates) we can read:

ExpoKit : To change the value of enabled , edit ios/<PROJECT-NAME>/Supporting/EXShell.plist and android/app/src/main/java/host/exp/exponent/generated/AppConstants.java . All other properties are set at runtime.

in Android’s AppConstants.java we can find ARE_REMOTE_UPDATES_ENABLED = true;
on iOS there’s no example on how to correctly set this value. is it simply areRemoteUpdatesEnabled ?
The doc is not clear, and this question was already asked on stackoverflow, but i can’t seem to find any answer to it.

Hey, thanks for pointing out that lack of info in the docs!

The key you want to set in EXShell.plist is:

<key>areRemoteUpdatesEnabled</key>
	<false/>

Thanks, my problem is inverse, I can’t get remote updates to work on iOS. I set it to true, but no change.
I set the proper releaseChannel etc, but when i launch the app after publishing a new version, it is never updated.

Ah okay, gotchya. In that case, a few things:

  • Can you double check you are testing this correctly? With "fallbackToCacheTimeout": 0 , you need to run expo publish (using the same release channel your app was detached with)
  • Does the SDK version of your deployed IPA/APK match the version of your new JS update? If the versions are different, it will not pull the update.
  • If neither of those are the case, could you post your app.json?

Hello Charlie, and thank you for your answer.

fallbackToCacheTimeout: I have it at 30000 because i want the update to be downloaded on start, so we don’t ask our test user to start it, kill it, restart it each time.

The SDK version matches, apart if I messed up something in the iOS native configuration (EXShell.plist etc)

Important info I noticed today:

  • OTA works perfect on Android
  • OTA does not work on iOS

So it seems to be an issues stricly iOS related.

I finished updating the project to the latest expo/expo kit sdk, so i’m in sdk34 now. Problem remains the same.

Here is my app.json:

{
  "expo": {
    "name": "app-name",
    "description": "A very interesting project.",
    "slug": "app-slug",
    "privacy": "unlisted",
    "sdkVersion": "34.0.0",
    "platforms": [
      "ios",
      "android"
    ],
    "version": "1.0.7",
    "orientation": "portrait",
    "icon": "./assets/images/icon.png",
    "splash": {
      "image": "./assets/images/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "notification": {
      "icon": "./assets/images/notification_icon.png",
      "color": "#34445C",
      "androidMode": "collapse",
      "androidCollapsedTitle": "New notifications"
    },
    "updates": {
      "enabled": true,
      "checkAutomatically": "ON_LOAD",
      "fallbackToCacheTimeout": 30000
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.app.name",
      "publishBundlePath": "ios/app-name/Supporting/shell-app.bundle",
      "publishManifestPath": "ios/app-name/Supporting/shell-app-manifest.json",
      "infoPlist": {
        "NSCameraUsageDescription": "...",
        "NSMicrophoneUsageDescription": "..."
      }
    },
    "android": {
      "package": "com.app.name",
      "permissions": [],
      "publishBundlePath": "android/app/src/main/assets/shell-app.bundle",
      "publishManifestPath": "android/app/src/main/assets/shell-app-manifest.json"
    },
    "isDetached": true,
    "scheme": "exp62609425f60b4d0cb1a06832401aaf06"
  }
}

and my EXShell.plist

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>areRemoteUpdatesEnabled</key>

<true/>

<key>isManifestVerificationBypassed</key>

<true/>

<key>isShell</key>

<true/>

<key>manifestUrl</key>

<string>exp://exp.host/@accountname/app-name</string>

<key>releaseChannel</key>

<string>preprod-sdk34</string>

</dict>

</plist>

Hey @charliecruzan , did you get a chance to take a look it :slight_smile: ?

I would ensure that this release channel:

releaseChannel
preprod-sdk34

is the correct one you’re trying to update. You should be using the same release channel your app was detached with

Well, when I detached the app, there was no release channel yet.
I started deploying on those channels when we needed an app on Testflight connected to the preprod API, and a release app on the Appstore connected to the prod API.
So it is crucial for me to be able to update the release channel.

I use the same release channels on android and OTA works fine.

What could have I missed on iOS?

PS: the releaseChannel from EXShell.list is actually automatically set by expo after i run “expo publish --release-channel preprod-sdk34

The only thing that could be missed is if the iOS build was made in a different release channel from the one you’re currently publishing to (so preprod-sdk34)

I’m building in release mode straight on my phone, with this setting (releaseChannel: preprod-sdk34)

The app starts, i modify my JS, publish to this channel, restart the app, no update happen.
If i do exactly the same on Android, the update works, the modifications show right away at the next launch.

I’ve been struggling with this for weeks and it’s highly handicapping us in our update cycle, as iOS is the slowest platform to update, due to their submission/review process.

I’ve really really digged deep, something else must be broken somewhere.

Any news @charliecruzan ?

This sounds exactly like what I am dealing with now. Very frustrating and I am not sure what else to try.

We ejected to ExpoKit last year while on SDK 32. We were able to build, deploy and issue OTA updates. We are now updating to SDK 35 and I can publish, build and deploy but subsequent OTA updates do not load within the TestFlight app.

The release channel from my published updates matches the last iOS build. I do not have an override for isManifestVerificationBypassed. I do not have overrides in app.json for updates. I must be missing something in one of the /ios files.

Here is my shell-app-manifest:

{
  "android": {
    "package": "com.americastestkitchen.groceryapp",
    "publishBundlePath": "android/app/src/main/assets/shell-app.bundle",
    "publishManifestPath": "android/app/src/main/assets/shell-app-manifest.json",
    "splash": {
      "backgroundColor": "#D73A15",
      "hdpi": "./assets/splash/splash.png",
      "hdpiUrl": "https://d1wp6m56sqw74a.cloudfront.net/~assets/59d4f26b507c7cc048e46769c1eb84dd",
      "ldpi": "./assets/splash/splash.png",
      "mdpi": "./assets/splash/splash.png",
      "mdpiUrl": "https://d1wp6m56sqw74a.cloudfront.net/~assets/59d4f26b507c7cc048e46769c1eb84dd",
      "xdpi": "./assets/splash/splash.png",
      "xxdpi": "./assets/splash/splash.png",
      "xxxdpi": "./assets/splash/splash.png"
    },
    "versionCode": 17
  },
  "androidShowExponentNotificationInShellApp": true,
  "androidStatusBar": {
    "backgroundColor": "#D73A15",
    "barStyle": "light-content"
  },
  "dependencies": [
    "@redux-offline/redux-offline",
    "algoliasearch",
    "babel-eslint",
    "bluebird",
    "bugsnag-react-native",
    "cheerio",
    "cloudinary-core",
    "dotenv",
    "elasticlunr",
    "expo",
    "expo-blur",
    "expo-constants",
    "expo-file-system",
    "expo-font",
    "expo-keep-awake",
    "expo-linear-gradient",
    "expo-local-authentication",
    "expo-web-browser",
    "formik",
    "html-truncate",
    "iap-receipt-validator",
    "jwt-decode",
    "lodash.debounce",
    "lodash.intersection",
    "lodash.memoize",
    "lodash.without",
    "pg-promise",
    "prop-types",
    "query-string",
    "react",
    "react-native",
    "react-native-billing",
    "react-native-dotenv",
    "react-native-gesture-handler",
    "react-native-htmlview",
    "react-native-iap",
    "react-native-in-app-utils",
    "react-native-mixpanel",
    "react-native-reanimated",
    "react-native-svg",
    "react-native-swipe-gestures",
    "react-native-unimodules",
    "react-navigation",
    "react-navigation-stack",
    "react-navigation-tabs",
    "react-redux",
    "redux",
    "redux-logger",
    "redux-promise-middleware",
    "redux-thunk",
    "request-promise",
    "s3",
    "styled-components",
    "tmp"
  ],
  "description": "blah",
  "detach": {
    "androidExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/android-v2.13.0-sdk35.0.0-b816b7af-88da-4ca9-87a5-7438f0c21b6e.tar.gz",
    "iosExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/ios-v2.13.0-sdk35.0.0-a30ebc9b-3db4-42f4-b677-e468076baf18.tar.gz",
    "scheme": "expabaa891af09c488b83d3144fc37a1f52"
  },
  "icon": "./assets/images/ATK.png",
  "iconUrl": "https://d1wp6m56sqw74a.cloudfront.net/~assets/d35c517280c9c0734abc77f4f964fd49",
  "ios": {
    "appStoreUrl": "https://itunes.apple.com/us/app/atk-grocery-app/id1365223384?ls=1&mt=8",
    "buildNumber": "13",
    "bundleIdentifier": "com.americastestkitchen.groceryapp",
    "publishBundlePath": "ios/atk-grocery-app/Supporting/shell-app.bundle",
    "publishManifestPath": "ios/atk-grocery-app/Supporting/shell-app-manifest.json",
    "supportsTablet": true
  },
  "isDetached": true,
  "locales": {},
  "name": "ATK Grocery App",
  "platforms": [
    "android",
    "ios"
  ],
  "primaryColor": "#D73A15",
  "scheme": "expb0e427fb50d841e9b7cc3c52adf7af61",
  "sdkVersion": "35.0.0",
  "slug": "atk-grocery",
  "splash": {
    "backgroundColor": "#D73A15",
    "image": "./assets/splash/splash.png",
    "imageUrl": "https://d1wp6m56sqw74a.cloudfront.net/~assets/59d4f26b507c7cc048e46769c1eb84dd"
  },
  "version": "1.2",
  "id": "@domain-management/atk-grocery",
  "revisionId": "1.2-r.YX2jN0EV4r",
  "publishedTime": "2019-09-27T02:31:20.152Z",
  "commitTime": "2019-09-27T02:31:20.242Z",
  "bundleUrl": "https://d1wp6m56sqw74a.cloudfront.net/%40domain-management%2Fatk-grocery%2F1.2%2Faf5f19ecf78e141652917fff79ac4c86-35.0.0-ios.js",
  "releaseChannel": "dev-1.3",
  "hostUri": "exp.host/@domain-management/atk-grocery?release-channel=dev-1.3"
}

here is my app.json

{
  "expo": {
    "name": "ATK Grocery App",
    "description": "blah",
    "slug": "atk-grocery",
    "sdkVersion": "35.0.0",
    "version": "1.2",
    "primaryColor": "#D73A15",
    "splash": {
      "image": "./assets/splash/splash.png",
      "backgroundColor": "#D73A15"
    },
    "androidStatusBar": {
      "barStyle": "light-content",
      "backgroundColor": "#D73A15"
    },
    "androidShowExponentNotificationInShellApp": true,
    "icon": "./assets/images/ATK.png",
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.americastestkitchen.groceryapp",
      "buildNumber": "13",
      "appStoreUrl": "https://itunes.apple.com/us/app/atk-grocery-app/id1365223384?ls=1&mt=8",
      "publishBundlePath": "ios/atk-grocery-app/Supporting/shell-app.bundle",
      "publishManifestPath": "ios/atk-grocery-app/Supporting/shell-app-manifest.json"
    },
    "android": {
      "package": "com.americastestkitchen.groceryapp",
      "versionCode": 17,
      "splash": {
        "backgroundColor": "#D73A15",
        "ldpi": "./assets/splash/splash.png",
        "mdpi": "./assets/splash/splash.png",
        "hdpi": "./assets/splash/splash.png",
        "xdpi": "./assets/splash/splash.png",
        "xxdpi": "./assets/splash/splash.png",
        "xxxdpi": "./assets/splash/splash.png"
      },
      "publishBundlePath": "android/app/src/main/assets/shell-app.bundle",
      "publishManifestPath": "android/app/src/main/assets/shell-app-manifest.json"
    },
    "isDetached": true,
    "detach": {
      "scheme": "expabaa891af09c488b83d3144fc37a1f52",
      "iosExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/ios-v2.13.0-sdk35.0.0-a30ebc9b-3db4-42f4-b677-e468076baf18.tar.gz",
      "androidExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/android-v2.13.0-sdk35.0.0-b816b7af-88da-4ca9-87a5-7438f0c21b6e.tar.gz"
    },
    "scheme": "expb0e427fb50d841e9b7cc3c52adf7af61"
  }
}

EXShell.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>isShell</key>
	<true/>
	<key>manifestUrl</key>
	<string>exp://exp.host/@domain-management/atk-grocery</string>
	<key>releaseChannel</key>
	<string>dev-1.3</string>
</dict>
</plist>

EXSDKVersions.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>detachedNativeVersions</key>
	<dict>
		<key>kernel</key>
		<string>35.0.0</string>
		<key>shell</key>
		<string>35.0.0</string>
	</dict>
	<key>sdkVersions</key>
	<array>
		<string>35.0.0</string>
	</array>
</dict>
</plist>

EXBuildConstants.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>DEFAULT_API_KEYS</key>
	<dict>
		<key>AMPLITUDE_KEY</key>
		<string>1e246ef3dacaabe8648768d7c35fceb1</string>
		<key>GOOGLE_MAPS_IOS_API_KEY</key>
		<string></string>
	</dict>
	<key>EXPO_RUNTIME_VERSION</key>
	<string>35.0.0</string>
	<key>STANDALONE_CONTEXT_TYPE</key>
	<string>user</string>
	<key>TEMPORARY_SDK_VERSION</key>
	<string>35.0.0</string>
	<key>developmentUrl</key>
	<string>expb0e427fb50d841e9b7cc3c52adf7af61://192.168.1.13:19000</string>
</dict>
</plist>

Any help or pointers would be very much appreciated!

Update! Figured out the solution for my problem. At some point during the upgrade process (reading blog posts, SO posts, etc), I changed some values in Info.plist by adding the following:

	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
		<key>NSAllowsArbitraryLoadsInWebContent</key>
		<true/>
		<key>NSAllowsLocalNetworking</key>
		<true/>
		<key>NSExceptionDomains</key>
		<dict/>
	</dict>

This caused an error when trying to fetch the updates:
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

A quick google for that pointed to the above directives. The solution was to revert my code back to:

	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>

I don’t recall (and can’t find right now) why I made the change in the first place. If I find the source of this change I may put a note there for future users.

For the record, I was able to find the above error by using the Updates package to manually fetch an update when my app mounted, catching the error and reporting to our error service (Bugsnag). The following method was called within componentDidMount in App.js.

  async checkForUpdates() {
    try {
      const update = await Updates.checkForUpdateAsync();
      if (update.isAvailable) {
        await Updates.fetchUpdateAsync();
        Updates.reloadFromCache();
      }
    } catch (e) {
      bugsnag.notify(e);
      console.log('Unable to process updates', e);
    }
  }

At this time my app is successfully processing OTA updates. The above checkForUpdates code has been removed.

1 Like

YESSSSSSSSS! Thank you so much!
This was the issue, my NSAllowsArbitraryLoads was set to false, for security concerns.
Finally after one year looking for the fix.

Isn’t this a security issue to leave it at true though @charliecruzan ?

Why is that a security issue?

Maybe this article explains it? I had never heard of NSAllowsArbitraryLoads (or ATS) before seeing this thread, but if I understand correctly, setting it to true causes iOS to allow “the use of cleartext protocols, invalid self-signed certificates for TLS connections, and weak cipher suites” when the app talks to a server.

@charliecruzan, if I have misunderstood, please enlighten me :slight_smile:. Also, why would that setting be needed for OTA updates? Those come from S3, right? And surely Amazon does not use insecure cipher suites etc?

Perhaps someone trying to do this should run a packet sniffer (e.g. Wireshark) to see what requests are being made and what fails when turning this off vs. turning it on. That should give us a better idea of why this setting affects OTA updates.