Skip to content

Apple Hosted Content

Jean-Christophe Hoelt edited this page May 28, 2019 · 3 revisions

Hosting in-app purchase content with Apple

How it works

Apple offers the option to host non-consumable content on its servers, which is automatically downloaded to the device on successfully purchasing a non-consumable IAP (see the documentation in the Apple Dev Center for more on this).

Once the purchase has been completed, the plugin checks with the StoreKit framework to see if any there is any Apple-hosted content associated with the purchased IAP. If so, the plugin initiates downloading of the content via StoreKit. The content is natively downloaded by StoreKit to the app's cacheDirectory as a zip file whose path is passed back to the plugin.

Adding payload files

In order to host content on Apple's servers, when creating the entry in-app purchase entry in iTunes Connect, ensure the "Hosting Content with Apple" radio button is set to "Yes" (it's "No" by default).

You then need to create an Xcode project for the in-app purchase which will be used to generate a package containing your payload files to upload to Apple's servers. To create a new in-app purchase project in XCode, select File > New > Project > Other > In-App Purchase. Set the Product Name to be your unique name for IAP within the app and the Organization Identifier to be the package ID of your app that will contain the in-app purchase.

Content files which are to be deployed when the hosted in-app purchase is downloaded should be placed within the in-app purchase Xcode project directory. It doesn't matter if they are placed alongside the xcodeproj folder or within the subdirectory alongside the ContentInfo.plist.

However it is important that you create references for these files within the Xcode project or they will not get deployed when the hosted content package is downloaded - see here for details how to do this.

Deployed content location

By default, the plugin recursively copies the content of the zip file to the app's documentsDirectory. See this repo for an example project that demonstrates this. For example, if a file called payload.txt is referenced in the project, it will be deployed to {app ID}/Documents/payload.txt. Using the Cordova File API, you'd access it something like this:

window.resolveLocalFileSystemURL(cordova.file.documentsDirectory + 'payload.txt', function(fileEntry){
  console.log("Absolute path to file: " + fileEntry.toURL());
},onError);

The plugin supports using the Folder key within the ContentInfo.plist file to specify a target subdirectory within the documentsDirectory. This is useful if you have many hosted content packages and want to keep the files in seperate directories. See this repo for an example project that demonstrates this. For example, if a file called payload.txt is referenced in the project, you can set the folder in the plist:

<key>Folder</key>
<string>nonconsumablehosted1</string>

Then it will be deployed to {app ID}/Documents/nonconsumablehosted1/payload.txt. Using the Cordova File API, you'd access it something like this:

window.resolveLocalFileSystemURL(cordova.file.documentsDirectory + 'nonconsumablehosted1/payload.txt', function(fileEntry){
  console.log("Absolute path to file: " + fileEntry.toURL());
},onError);

FAQ

Will the downloaded files be synced to iCloud?

No. The plugin sets an attribute on the downloaded files to prevent syncing to iCloud.

.progress() stops being called at 80% (it never reaches 100%) then .downloaded is called()

The .progress() callback is invoked directly by the native iOS StoreKit framework when the product download is ACTIVE. The progress percentage is generated by the StoreKit framework, not by the plugin. The plugin simply passes along the information passed to it by StoreKit, which is what performs and monitors the download from the Apple servers. StoreKit doesn't invoke the ACTIVE state when the download reaches 100%, it invokes the FINISHED, which in turn calls (.downloaded()). So in practice, .progress() will not be called when download reaches 100%, it will only be called on the preceding percentage step. It's up to you how you handle this in your app.

Troubleshooting

If you're having issues with downloaded content and are able to build and run your app locally using XCode, you can switch on debug output from the plugin's Objective-C component to see exactly what/where it is deploying content. To do this, set store.verbosity = store.DEBUG; before calling any other store functions. Then run the app via XCode to see the Objective-C debug output in the XCode console. The output will look something like this:

2015-09-23 21:48:55.629 PurchaseDemo[665:148599] InAppPurchase[objc]: Copying downloaded content to Documents...
2015-09-23 21:48:55.630 PurchaseDemo[665:148599] InAppPurchase[objc]: Creating Documents subfolder: /var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Documents/nonconsumablehosted1
2015-09-23 21:48:55.632 PurchaseDemo[665:148599] InAppPurchase[objc]: No Files key found in .plist - copy all files in Content folder
2015-09-23 21:48:55.633 PurchaseDemo[665:148599] InAppPurchase[objc]: Content path: /private/var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Library/Caches/2086C567-9107-434B-8104-043BDBE45AA2.zip/Contents/5MB.txt
2015-09-23 21:48:57.695 PurchaseDemo[665:148599] InAppPurchase[objc]: Copied /private/var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Library/Caches/2086C567-9107-434B-8104-043BDBE45AA2.zip/Contents/5MB.txt to /var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Documents/nonconsumablehosted1/5MB.txt
2015-09-23 21:48:57.697 PurchaseDemo[665:148599] InAppPurchase[objc]: Content path: /private/var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Library/Caches/2086C567-9107-434B-8104-043BDBE45AA2.zip/Contents/payload.txt
2015-09-23 21:48:57.709 PurchaseDemo[665:148599] InAppPurchase[objc]: Copied /private/var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Library/Caches/2086C567-9107-434B-8104-043BDBE45AA2.zip/Contents/payload.txt to /var/mobile/Containers/Data/Application/6678AE31-9115-4C7F-B6DE-328DB057C5A5/Documents/nonconsumablehosted1/payload.txt