Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cordova iOS crashes when targeting remote server

I have a cordova app built on Angular 1.6 and Ionic v1. I'm facing a terrible issue on iOS and I don't even know what's going wrong. I'll explain the problem and what I've tried so far, hopefully someone will shed some light on this.

The Problem
We have a screen which is a simple form, you fill in some text and add attachments if you like. For attachments, you can:

  • Take a picture from your camera
  • Shoot a video with you camera
  • Record audio
  • Choose from your library
  • Choose from your iCloud Drive (iOS) or the File System (Android)

You can then save the record, which stores everything on the file system. Or upload to server, which again stores the record on your device.

The issue is, when I choose a file from the library, or any other source, the app crashes unexpectedly shortly after. I can add attachments and save/upload but when I navigate away, the app crashes. This only happens on iOS. No errors, no warnings, no debuggable output, just crashes. I examined the crash logs on my iPhone and apparently the main thread is being blocked for over 5sec. Which causes a watchdog transgression exception to be thrown. Hard to tell what's causing this thread lock, no idea.

I'm using an iPhone 8 running iOS 12.1. It's worth mentioning that the app works fine on the simulator, no bugs or crashes there.

What I've Tried So Far
At first, I thought perhaps something's wrong with my code. So I reviewed every code file line by line, refactored my JS code and improved the code quality. Made sure promises work as intended, resolved JSLint/TSLint warnings etc.

I have updated all cordova plugins to their latest versions. Also removed both platforms and added the latest versions. None of them helped. So I thought maybe I'm missing a configuration quirk or something. Dug through github documentations and SO threads, couldn't find anything useful. Some other things that I've tried:

  • Disabled HTTPS on our production server, and sent everything through HTTP
  • Added NSAppTransportSecurity settings to the *.plist file, and white-listed our domain
  • Tinkered with the Content-Security-Policy, even removed it altogether
  • Privacy Descriptions are properly configured (NSCameraUsage etc.)

None of them worked. I've been struggling with this issue for two weeks now.

The Weird Part
What's confusing me, is that when I target my local dev machine, that is, when I set the base URL for my API calls to point to my local IIS, the apps works perfectly fine. No bugs/errors, no crashes.

But when I target our remote server, the app crashes when I try to work with attachments (camera, iCloud etc.). I have no idea what I'm missing here. There are no differences between my machine and our remote server. Both run the exact same software, same configuration, and the mobile app is the same build, running on the same device.

So what I can say for certain, is that this issue has nothing to do with my application code, or Cordova and its plugins. The same build works perfectly when targeting my local IIS.

My app is already in production and this needs to be fixed NOW. This is driving me mad, I've tried everything that I could possibly think of and still, no luck. Has anyone had a similar issue? Any help is appreciated.

I'm not authorized to share my code, and like I said, there's nothing wrong with the code, it works absolutely fine when targeting my local IIS. But for your reference, here's some information about my project.

Preferences in Config.xml

<preference name="SplashScreen" value="screen" />
<preference name="windows-target-version" value="10.0" />
<preference name="AndroidPersistentFileLocation" value="Internal" />
<preference name="iosPersistentFileLocation" value="Library" />
<preference name="webviewbounce" value="false" />
<preference name="UIWebViewBounce" value="false" />
<preference name="DisallowOverscroll" value="true" />
<preference name="BackupWebStorage" value="local" />

Cordova Plugins

<plugin name="cordova-plugin-geolocation" spec="^2.4.3">
    <variable name="GEOLOCATION_USAGE_DESCRIPTION" value="Location access allows you to capture your geolocation information on to your records." />
</plugin>
<plugin name="cordova-plugin-device" spec="^1.1.7" />
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
<plugin name="cordova-plugin-app-icon-changer" spec="^1.0.0" />
<plugin name="es6-promise-plugin" spec="^4.2.2" />
<plugin name="cordova-plugin-ios-camera-permissions" spec="^1.2.0">
    <variable name="CAMERA_USAGE_DESCRIPTION" value="Camera access allows you to capture and attach photos that you take to your records." />
    <variable name="MICROPHONE_USAGE_DESCRIPTION" value="Microphone access allows you to capture voice information to your records." />
    <variable name="PHOTOLIBRARY_ADD_USAGE_DESCRIPTION" value="Photo library access allows you to upload your photos and media files to your records." />
    <variable name="PHOTOLIBRARY_USAGE_DESCRIPTION" value="Photo library access allows you to upload your photos and media files to your records." />
</plugin>
<plugin name="cordova-plugin-android-fingerprint-auth" spec="^1.4.1" />
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
<plugin name="cordova-plugin-filechooser" spec="1.1.0" />
<plugin name="cordova-plugin-crosswalk-webview" spec="2.4.0">
    <variable name="XWALK_VERSION" value="23+" />
    <variable name="XWALK_LITEVERSION" value="xwalk_core_library_canary:17+" />
    <variable name="XWALK_COMMANDLINE" value="--disable-pull-to-refresh-effect" />
    <variable name="XWALK_MODE" value="embedded" />
    <variable name="XWALK_MULTIPLEAPK" value="true" />
</plugin>
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
<plugin name="cordova-plugin-add-swift-support" spec="1.7.2" />
<plugin name="cordova-plugin-touch-id" spec="3.4.0">
    <variable name="FACEID_USAGE_DESCRIPTION" value="OnRecord would like to access your touch ID to let you log in securely." />
</plugin>
<plugin name="cordova-plugin-media-playback" spec="1.0.2-dev5" />
<plugin name="cordova-plugin-documentpicker" spec="1.0.0" />
<plugin name="cordova-plugin-file" spec="6.0.1" />
<plugin name="cordova-plugin-file-transfer" spec="1.7.1" />
<plugin name="cordova-plugin-media-capture" spec="3.0.2" />
<plugin name="cordova-plugin-camera" spec="4.0.3" />

Content-Security-Policy in index.html

<meta http-equiv="Content-Security-Policy" content="default-src 'self' gap://ready ms-appdata file://* *; img-src 'self' content: android-webview-video-poster: data: *; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maps.googleapis.com https://maps.gstatic.com; media-src *; connect-src *">

App Transport Security Policies

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSAllowsArbitraryLoadsInWebContent</key>
  <true/>
  <key>NSAllowsLocalNetworking</key>
  <true/>
  <key>NSExceptionDomains</key>
  <dict>
      <key>your.domain.com</key>
      <dict>
          <key>NSIncludesSubdomains</key>
          <true/>
          <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
          <true/>
          <key>NSTemporaryExceptionMinimumTLSVersion</key>
          <string>1.0</string>
          <key>NSTemporaryExceptionRequiresForwardSecrecy</key>
          <false/>
      </dict>
  </dict>
</dict>

Privacy Descriptions (Permissions)

<key>NSFaceIDUsageDescription</key>
<string>This app would like to access your touch ID to let you log in securely.</string>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app would like to access your location to let you track your records.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs write-access to photo library</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs read/write-access photo library access</string>

Let me know if you need more information or any further explanations. I've tried to describe the problem best as I can. To recap:

The app works flawlessly when targeting my local machine, but crashes when targeting our remote server. What's bugging me is, this shouldn't have anything to do with my app. The camera plugin, choosing files from different sources etc. happen locally on the device, what's it got to do with my base API address?! Very strange indeed.

Update: Clarification
After more test runs, I have figured out what's causing the app to crash. Although it's still not clear, from a UX perspective, this is what happens on iOS:

As soon as I use the Camera plugin (cordova-camera), the app crashes shortly after. It doesn't matter if I choose a file from the camera roll/library, or take a picture etc. I just open the camera or library, cancel and navigate away. The app crashes. So clearly it has something to do with the camera plugin.

What's bugging me is that, as I mentioned before, when I target my local IIS by changing the base URL address, the app works fine. I don't understand why this has anything to do with camera usage, because it happens locally on the device. What I'm speculating now is, perhaps something is causing the app to throw exceptions because the remote URL uses HTTPS. But I'm not getting any warnings/errors in Xcode, so who knows.

Certainly, the issue is not with cordova-ios, the camera plugin, my JS code, or any security configurations (App Transport Security, and Content-Security-Policy). Because the app works fine when targeting my IIS. I think I'm missing something here.

like image 970
Nexus Avatar asked Dec 18 '18 07:12

Nexus


1 Answers

I finally found what's causing the iOS app to crash. The problem was in our code but still, it had something to do with Cordova as well, specifically the File and Camera plugins.

We had a TypeScript class in our project that was developed by someone else long ago. This class handled data access uinsg the File plugin and the native file system. We were using it to store JSON objects on the device, which was working fine, but only for small cases like saving a file or two. You fetch some data from the server, and for each record we create a JSON file on the file system and store it. The problem occured when we used this in a loop. Say you get a 100 records back from the server, and within a loop you call the save method to store the records.

This worked fine, but immediately after using the Camera plugin (or sometimes other plugins like the iCloud Document Picker) the app crashed silently. I'm guessing the File plugin or the write operations were causing memory leaks, or the app to go low on memory, subsequently accessing the Camera plugin crashed the app. I'm not quite sure why everything worked fine on Android, perhaps because the Cordova engine and the Android file system are different.

Nevertheless, there was no need to store JSON data on the file system. So I refactored the project to use LocalStorage instead. It's so much faster and it also resolved the issue. No crashes on iOS! It might be that the code for writing to the file system could be changed to fix the issue, but it wasn't necessary anyway.

I'm glad I have finally figured this out, but it's a bloody nightmare to debug Cordova plugins and spot memory leaks. The only thing left is to replace LocalStorage with something more reliable like SQLite. Because the OS can decide to clear out the data when low on memory/space and we have no control over it. For now, it's totally fine. I've been trying to use the Ionic Storage module because it uses SQLite and allows storing JSON or key/value pairs. But I haven't been able to use the module in Angular 1 which is a bummer because I can use other Ionic Native modules just fine. Case closed.

like image 71
Nexus Avatar answered Dec 04 '22 04:12

Nexus