In last week’s article about getting the best out of the App Store app, I promised to explore the app and Store in greater detail, which this article covers. On this occasion, I’m not going to look at the purchase process, but how the App Store app obtains and installs already-purchased apps and updates. This is based on both Intel and M1 versions of the App Store app in macOS 11.3, explored using the unified log and tools such as Little Snitch. Fans of Apple internal code names are in for a field day.
The principal sub-systems involved in these transactions include:
Although app install and update are long and elaborate series of processes, log entries for them appear remarkably consistent and share many common sequences. After some initial preparations by com.apple.AppleMediaServices, appstored announces both downloads and updates as a store purchase.
00.839827 [PurchaseService]: [F561841A] Starting purchase of 1037126344:com.apple.configurator.ui by com.apple.AppStore
For an update, com.apple.appstored then checks the purchase history of that item. If that confirms the item to be updated has already been purchased correctly, its sequence proceeds.
com.apple.AppleMediaServices next performs an AMSPurchaseTask, in which the initial step is to generate a “fraud score” based on the user’s account details including first name, last name, phone number and username. When that has been completed successfully, com.apple.AppleMediaServices invokes a series of intriguingly named services:
- AMSAnisette, whose headers are fetched;
- AMSAbsinthe, which appears to be skipped in these cases;
- AMSMescal, which also appears to be skipped.
Installing an already-purchased app involves sending an encoded request to [https:] p24-buy.itunes. apple.com, but update requests are sent instead to [https:] downloaddispatch.itunes. apple.com. Given a successful response to that, the account details are changed to record the installation or update. A ‘bag’ is prepared and loaded, and with the involvement of com.apple.JetEngine JS loads and packages a resource. This ‘Jetpack’ is decrypted using CommonCrypto, and decompressed. The unpacked Jetpack is stored in ~/Library/Caches/com.apple.AppleMediaServices/Engagement/journeys/output/[UUID].bundle and a JS stack created and built. App databases in ~/Library/Caches/com.apple.AppleMediaServices/Engagement are consulted before the purchase is completed successfully
01.769682 AMSPurchaseTask: [UPDF561841A/com.apple.configurator.ui:1037126344] Purchase completed successfully
The next stage is the Purchase Import Task
01.770148 [Transaction] Started transaction: com.apple.appstored.PurchaseImportTask
This starts with a check of the app’s preflight pfpkg on the server [https:] osxapps.itunes. apple.com. com.apple.appstored creates a temporary download folder on a path like /var/folders/x4/[alphanumerics]/C/com.apple.appstoreagent/com.apple.appstore/[UUID] on the startup Data volume. appstoreagent then calls for a certificate check, and com.apple.appstored checks that there’s sufficient free disk space as part of its preflight evaluation.
When a new app is being installed, a placeholder image is downloaded from [https:] is4-ssl.mzstatic.com, but that isn’t required for updates. Preamble checks are then performed before starting the main AppInstallDownload Task. Anisette headers are fetched but once again Absinthe is skipped and Mescal disabled. The main app or update payload is downloaded to the temporary download folder. Near the start of that, com.apple.appstored ‘chains’ FairPlay decryption
01.937146 [UPDF561841A/com.apple.configurator.ui:1037126344] Chaining FairPlay decryption
Once download of the main payload starts, com.apple.appstored starts updating the progress circle displayed in the App Store app. This normally happens every second or more frequently, the proportion being given in log entries such as
02.346491 [UPDF561841A/com.apple.configurator.ui:1037126344]: Setting progress: 0.00 for bundleID: com.apple.configurator.ui
com.apple.AppStoreKit also gives progress in thousandths. When the app or update is being fetched from a local Content Caching Server, this download phase can be so brief as to be momentary, but it’s normally complete when the progress value reaches 0.80, after which the post-download phase starts.
For updates, com.apple.appstored then sets the path to the app to be updated
03.507853 Setting update path to /Applications/Apple Configurator 2.app for com.apple.configurator.ui
Following extensive changes to containers and metadata, and certificate checks, which are often slower than downloading an update, installation proceeds again. During those checks, it appears that notarization, XProtect and MRT checks may also be performed, despite there being no quarantine flag set.
Three extended attributes are set on the app: com.apple.appstore.store_cohort, com.apple.appstore.storefront and com.apple.appstore.vendor_name. A bundle record for the new app is built and registered with LaunchServices. Entitlements are checked, and remaining tasks performed to complete the installation or update. That’s announced as:
11.882464 Update completion received for: <SoftwareItem: 0x1447eac00>: (com.apple.configurator.ui, 2.14, 1037126344:840560161 md:0x0 /Applications/Apple Configurator 2.app)
com.apple.appstored then updates the Softare Map stored in ~/Library/Caches/com.apple.appstoreagent/SoftwareMap, which is a master index of all App Store apps installed on that Mac. The temporary download folder is removed.
com.apple.appstored informs Crossfire, which appears to monitor app usage, of the new or updated app which has just been installed. The installation process then enters its ‘postamble’ phase, in which any remaining clean-up is performed. The downloaded or updated app is then ready for use.
I will be adding a feature to my free utility Mints to extract log entries relevant to the download and updating of App Store apps, which should make it easier to diagnose problems.