Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I detect if the currently running app was installed from the app store?

Tags:

ios

app-store

Is there a way in iOS to programmatically check if the currently running app was installed from the iOS App Store? This is in contrast to an app that was run via Xcode, TestFlight, or any non-official distribution source.

This is in the context of an SDK that doesn't have access to the app's source code.

To be clear - I am looking for some signature, so to speak, given to the app (presumably by Apple), that will, without dependence on any preprocessor flags or other build configurations, be accessible to any application at run time.

like image 659
kevlar Avatar asked Aug 16 '13 21:08

kevlar


People also ask

How can I check if an app is installed from a Web page on an Iphone?

Show activity on this post. iOS Safari has a feature that allows you to add a "smart" banner to your webpage that will link either to your app, if it is installed, or to the App Store. You do this by adding a meta tag to the page.

How do you know if an app is installed iOS?

In the older software, there is no way of finding out exactly when an app was purchased, but in the latest software, you can go to the App Store and select Updates, then click on the top right to go to Purchased and see if the app was purchased (paid or free) and the date of install.


2 Answers

Apps downloaded from the App Store have a iTunesMetadata.plist file added by the store:

NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:@"iTunesMetadata.plist"]; if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {     // probably a store app } 

Perhaps you might want to check if this file exists.

Update:

In iOS8, the application bundle has been moved. According to @silyevsk, the plist is now one level above [the new application main bundle path], at /private/var/mobile/Containers/Bundle/Application/4A74359F-E6CD-44C9-925D-AC82E‌‌​​B5EA837/iTunesMetadata.plist, and unfortunately, this can't be accessed from the app (permission denied)

Update Nov 4th 2015:

It appears that checking the receipt name can help. It must be noted that this solution is slightly different: it doesn't return whether we're running an App Store app, but rather whether we're running a beta Testflight app. This might or might not be useful depending on your context.

On top of that, it's a very fragile solution because the receipt name could change at any time. I'm reporting it anyway, in case you have no other options:

// Objective-C BOOL isRunningTestFlightBeta = [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"];  // Swift let isRunningTestFlightBeta = NSBundle.mainBundle().appStoreReceiptURL?.lastPathComponent=="sandboxReceipt" 

Source: Detect if iOS App is Downloaded from Apple's Testflight

How HockeyKit does it

By combining the various checks you can guess whether the app is running in a Simulator, in a Testflight build, or in an AppStore build.

Here's a segment from HockeyKit:

BOOL bit_isAppStoreReceiptSandbox(void) { #if TARGET_OS_SIMULATOR   return NO; #else   NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;   NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;      BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];   return isSandboxReceipt; #endif }  BOOL bit_hasEmbeddedMobileProvision(void) {   BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];   return hasEmbeddedMobileProvision; }  BOOL bit_isRunningInTestFlightEnvironment(void) { #if TARGET_OS_SIMULATOR   return NO; #else   if (bit_isAppStoreReceiptSandbox() && !bit_hasEmbeddedMobileProvision()) {     return YES;   }   return NO; #endif }  BOOL bit_isRunningInAppStoreEnvironment(void) { #if TARGET_OS_SIMULATOR   return NO; #else   if (bit_isAppStoreReceiptSandbox() || bit_hasEmbeddedMobileProvision()) {     return NO;   }   return YES; #endif }  BOOL bit_isRunningInAppExtension(void) {   static BOOL isRunningInAppExtension = NO;   static dispatch_once_t checkAppExtension;      dispatch_once(&checkAppExtension, ^{     isRunningInAppExtension = ([[[NSBundle mainBundle] executablePath] rangeOfString:@".appex/"].location != NSNotFound);   });      return isRunningInAppExtension; } 

Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

A possible Swift class, based on HockeyKit's class, could be:

// //  WhereAmIRunning.swift //  https://gist.github.com/mvarie/63455babc2d0480858da // //  ### Detects whether we're running in a Simulator, TestFlight Beta or App Store build ### // //  Based on https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITHockeyHelper.m //  Inspired by https://stackoverflow.com/questions/18282326/how-can-i-detect-if-the-currently-running-app-was-installed-from-the-app-store //  Created by marcantonio on 04/11/15. //  import Foundation  class WhereAmIRunning {          // MARK: Public          func isRunningInTestFlightEnvironment() -> Bool{         if isSimulator() {             return false         } else {             if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() {                 return true             } else {                 return false             }         }     }          func isRunningInAppStoreEnvironment() -> Bool {         if isSimulator(){             return false         } else {             if isAppStoreReceiptSandbox() || hasEmbeddedMobileProvision() {                 return false             } else {                 return true             }         }     }      // MARK: Private      private func hasEmbeddedMobileProvision() -> Bool{         if let _ = NSBundle.mainBundle().pathForResource("embedded", ofType: "mobileprovision") {             return true         }         return false     }          private func isAppStoreReceiptSandbox() -> Bool {         if isSimulator() {             return false         } else {             if let appStoreReceiptURL = NSBundle.mainBundle().appStoreReceiptURL,                 let appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent                 where appStoreReceiptLastComponent == "sandboxReceipt" {                     return true             }             return false         }     }          private func isSimulator() -> Bool {         #if arch(i386) || arch(x86_64)             return true             #else             return false         #endif     }      } 

Gist: GitHub - mvarie/WhereAmIRunning.swift

Update Dec 9th 2016:

User halileohalilei reports that "This no longer works with iOS10 and Xcode 8.". I didn't verify this, but please check the updated HockeyKit source (see function bit_currentAppEnvironment) at:

Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

Over time, the above class has been modified and it seems to handle iOS10 as well.

Update Oct 6th 2020:

Hockey has been deprecated/abandoned and replaced by Microsoft's AppCenter SDK.

This is their App Store / Testflight build detection class (link to repository below code):

MSUtility+Environment.h :

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License.  #import <Foundation/Foundation.h>  #import "MSUtility.h"  /*  * Workaround for exporting symbols from category object files.  */ extern NSString *MSUtilityEnvironmentCategory;  /**  *  App environment  */ typedef NS_ENUM(NSInteger, MSEnvironment) {    /**    *  App has been downloaded from the AppStore.    */   MSEnvironmentAppStore = 0,    /**    *  App has been downloaded from TestFlight.    */   MSEnvironmentTestFlight = 1,    /**    *  App has been installed by some other mechanism.    *  This could be Ad-Hoc, Enterprise, etc.    */   MSEnvironmentOther = 99 };  /**  * Utility class that is used throughout the SDK.  * Environment part.  */ @interface MSUtility (Environment)  /**  * Detect the environment that the app is running in.  *  * @return the MSEnvironment of the app.  */ + (MSEnvironment)currentAppEnvironment;  @end 

MSUtility+Environment.m :

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License.  #import "MSUtility+Environment.h"  /*  * Workaround for exporting symbols from category object files.  */ NSString *MSUtilityEnvironmentCategory;  @implementation MSUtility (Environment)  + (MSEnvironment)currentAppEnvironment { #if TARGET_OS_SIMULATOR || TARGET_OS_OSX || TARGET_OS_MACCATALYST   return MSEnvironmentOther; #else    // MobilePovision profiles are a clear indicator for Ad-Hoc distribution.   if ([self hasEmbeddedMobileProvision]) {     return MSEnvironmentOther;   }    /**    * TestFlight is only supported from iOS 8 onwards and as our deployment target is iOS 8, we don't have to do any checks for    * floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1).    */   if ([self isAppStoreReceiptSandbox]) {     return MSEnvironmentTestFlight;   }    return MSEnvironmentAppStore; #endif }  + (BOOL)hasEmbeddedMobileProvision {   BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];   return hasEmbeddedMobileProvision; }  + (BOOL)isAppStoreReceiptSandbox { #if TARGET_OS_SIMULATOR   return NO; #else   if (![NSBundle.mainBundle respondsToSelector:@selector(appStoreReceiptURL)]) {     return NO;   }   NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;   NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;    BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];   return isSandboxReceipt; #endif }  @end 

Source: GitHub - microsoft/appcenter-sdk-apple - MSUtility+Environment.m

like image 142
magma Avatar answered Oct 12 '22 23:10

magma


If you're talking about your own app, you could add a state that returns true if it was build as part of a Store version (e.g. a compiler conditional) and false in every other case.

If you're talking about another app, it's not easy or straightforward (or maybe not even possible) to query other apps outside of your sandbox.

like image 44
Michael Dautermann Avatar answered Oct 12 '22 23:10

Michael Dautermann