Distributing and installing non-market IPA application on iPhone, over the air (OTA)

Adrian Stanecki
4 min readApr 12, 2019

--

A few weeks ago while distributing Fitatu on Google Play and AppStore I took a look at the process a little more closely. I noticed that testing still supported versions of application is not complete. Then I thought — what about custom tool for versioning these released (or not) apps? It sounds interesting and could be given under single QR Code per version, with distributing our app over-the-air (OTA). Why not? This solution means installing IPA application without TestFlight or the official App Store.

On the web, there are few tools which build and store given apps on their server. Learning something new and building it from scratch is worth more, isn’t it? Our solution is based on:

Firstly, let’s draw basic scheme of our solution and analyze general architecture.

What do you see on the scheme? We probably need the following:

  1. Front client application
  2. The server for sending requested Fitatu.ipa file

At first look it is just scan event on mobile phone, GET request to the server and sending sample Fitatu.ipa file. That’s right?

Client side taking into account the IPv4 address:

After that, we have to create server side. I decided to write these a few lines in express:

Please note, server responses without checking fitatu version. Just sends the same file version on each request. It seems to be really simple, but when scanning you should see following screen:

Currently each iOS device will not install our application as typical application installed from AppStore. It will be downloaded as file without executing install process. To help our iOS device in recognision given file we have to implement a little bit different action behind QR Code. This time query is:

http://192.168.0.87:3000/fitatu.ipa

On iOS, whole URL has to be prefixed by interested us action. We are not allowed to just install ipa file directly. We should load manifest:

itms-services://?action=download-manifest&url=http://192.168.0.87:3000/fitatu.ipa

Hm, look better so check this out. Scan code again. What is the result?

Here the biggest different between Android and iOS comes. Client on the front has to receive correctly defined manifest.plist file. Here you are sample:

As you see, our query is just prefixed by this action. Now you know we need to receive manifest.plist. Of course we can ask api for manifest.plist by URL based on /fitatu.ipa

itms-services://?
action=download-manifest&
url=http://localhost:3000/fitatu.ipa

but isn’t it readable? Write it more human friendly and change to:

itms-services://?
action=download-manifest&
url=http://localhost:3000/manifest.plist

Then handling /manifest.plist on the server side is also required:

Summary scheme:

There is also one thing that is not talked. Our app should hosted on an HTTPS server. With helps comes solution called Ngrok. It is free solution based on secure tunneling to localhost (https://ngrok.com/). Really nice tool. Our local server is given behind ngrok URL with SSL. When downloaded & installed, type from cmd:

> Ngrok.exe http 3000

Now all requests to localhost has to be changed to url generated by Ngrok, like

As we see, all requests sends to ngrok’s URL will be sended to our server. Here, https://5afcaede.ngrok.com will be local server with https. So QRCode on front side should also contains this change:

itms-services://?
action=download-manifest&
url=https://5afcaede.ngrok.com/manifest.plist

…and URL in manifest.plist also must be based on ngrok url with fitatu.ipa query

<key>url</key>
<string>https://5afcaede.ngrok.com/fitatu.ipa</string>

Now installing ipa works at your phone.

Today we saw how installing ipa on Apple platform works. I hope my schemes and manner to interpolation was simpler than apple rules ;) What about installing Fitatu.ipa on iOS and Fitatu.apk on Android from one url? Currently implemented code can be good base for it. Next time, in a few words we will get how the final tool for versioning apps is composed.

--

--