Determine whether app is communicating with APNS sandbox or production environment

11,803

Solution 1

You can read and check the embedded provisioning profile.

https://github.com/tcurdt/TCMobileProvision

This is what I do:

NSString *mobileprovisionPath = [[[NSBundle mainBundle] bundlePath]
        stringByAppendingPathComponent:@"embedded.mobileprovision"];
TCMobileProvision *mobileprovision = [[TCMobileProvision alloc] initWithData:[NSData dataWithContentsOfFile:mobileprovisionPath]];
NSDictionary *entitlements = mobileprovision.dict[@"Entitlements"];
NSString *apsEnvironment = entitlements[@"aps-environment"];
BOOL production = entitlements && apsEnvironment && [apsEnvironment isEqualToString:@"production"];

Solution 2

This is a hack but its working on XCode 8 with Swift 3

We're basically opening the embedded.mobileprovision file, converting it to a string, then checking for a string that would indicate the app is using the development aps-environment.

func isDevelopmentEnvironment() -> Bool {
    guard let filePath = Bundle.main.path(forResource: "embedded", ofType:"mobileprovision") else {
        return false
    }
    do {
        let url = URL(fileURLWithPath: filePath)
        let data = try Data(contentsOf: url)
        guard let string = String(data: data, encoding: .ascii) else {
            return false
        }
        if string.contains("<key>aps-environment</key>\n\t\t<string>development</string>") {
            return true
        }
    } catch {}
    return false
}

Solution 3

  1. The APNS environment is determined according to the code sign Entitlements matching your Code sign identity (good post here) - while identifying your build configuration may work, it may also be false if you've matched that build configuration with a mis-matched entitlement.

  2. Keeping that in mind, using DEBUG as a mean to determine your entitlements should work (if you find DEBUG to be tricky, you can add a your own linker flag under "Apple LLVM..." -> "Other C Flags" -> "Debug") for example, add -DDEBUGGING and then use:

#ifdef DEBUGGING BOOL isProd = YES; #else BOOL isProd = NO; #endif

Solution 4

I wanted to be sure to deserialize the plist in the provisioning profile instead of searching for strings.

import UIKit

public extension UIDevice {
    enum PushEnvironment: String {
        case unknown
        case development
        case production
    }

    var pushEnvironment: PushEnvironment {
        guard let provisioningProfile = try? provisioningProfile(),
              let entitlements = provisioningProfile["Entitlements"] as? [String: Any],
              let environment = entitlements["aps-environment"] as? String
        else {
            return .unknown
        }

        return PushEnvironment(rawValue: environment) ?? .unknown
    }

    // MARK: - Private

    private func provisioningProfile() throws -> [String: Any]? {
        guard let url = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") else {
            return nil
        }

        let binaryString = try String(contentsOf: url, encoding: .isoLatin1)

        let scanner = Scanner(string: binaryString)
        guard scanner.scanUpToString("<plist") != nil, let plistString = scanner.scanUpToString("</plist>"),
              let data = (plistString + "</plist>").data(using: .isoLatin1)
        else {
            return nil
        }

        return try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]
    }
}

You can use this to do something like:

UIDevice.current.pushEnvironment == .production

When I tell my API about my device, I include UIDevice.current.pushEnvironment.rawValue and the API can use the proper certificate and gateway to send the device notifications.

Share:
11,803
goldierox
Author by

goldierox

Updated on July 17, 2022

Comments

  • goldierox
    goldierox almost 2 years

    I have push notifications set up in my app. I'm trying to determine whether the device token I've received from APNS in the application:didRegisterForRemoteNotificationsWithDeviceToken: method came from the sandbox or development environment. If I can distinguish which environment initialized the token, I'll be able to tell my server to which environment to send the push notification.

    I've tried using the DEBUG macro to determine this, but I've seen some strange behavior with this and don't trust it to be 100% correct.

    #ifdef DEBUG
    BOOL isProd = YES;
    #else
    BOOL isProd = NO;
    #endif
    

    Ideally, I'd be able to examine the aps-environment entitlement (value is Development or Production) in code, but I'm not sure if this is even possible.

    What's the proper way to determine whether your app is communicating with the APNS sandbox or production environments? I'm assuming that the server needs to know this in the first place. Please correct me if this is assumption is incorrect.

    Edited: Apple's documentation on Provider Communication with APNS details the difference between communicating with the sandbox and production. However, the documentation doesn't give information on how to be consistent with registering the token (from the iOS client app) and communicating with the server.

  • goldierox
    goldierox about 12 years
    Thanks for the response, @Wiz. I ended up using a boolean in the configuration plist file. The only catch, as you mentioned, is that this configuration setting has to match the code signing identity, which is in the project file. It's undesirable but the best solution available.
  • Tom Andersen
    Tom Andersen over 11 years
    Agreed. Apple could easily provide API to tell you what the current entitlement is - then you could set production up, or route sandboxed tokens to your sandbox server...
  • Albert Bori
    Albert Bori over 9 years
    The library is a bit outdated, but it worked nicely for grabbing the aps-environment (replace "get-task-allow" with "aps-environment" in @tcurdt 's example, and don't compare it as a bool)
  • tcurdt
    tcurdt over 9 years
    @AlbertBori could you please file an issue on what is outdated?
  • tcurdt
    tcurdt over 8 years
    Fixed the warning. Let me know if more needs updating.
  • KeithB
    KeithB over 7 years
    phatmann, thanks for the Swift code. I'm curious if you had a reason to check a boolean state of Sandbox and not return true if on Production? What I found was that if I'm running a local build from xCode it fails when attempting to load 'mobileprovision', which causes a return of false (indicating that it is NOT in the sandbox). However, if I create a sandbox provisioned Archive file that does successfully load the mobileprovision file and indicates correctly that this is a Sandbox build. This being the case it seems necessary to test for successfully finding "production".
  • KeithB
    KeithB over 7 years
    This no longer seems to work in xCode 8.1, iOS 10. Here's what I'm seeing. If I build and distribute my app via HockeyApp, then the embeddded mobileprovision file is found and mobileProvision.dict["Entitlements"] = 'development'. However, when I upload to ITunes for distribution via TestFlight it fails. Note, that when going through testflight all push notifications are executed on the Production APNS, even though the app is in the testing phase. Any updates?
  • phatmann
    phatmann over 7 years
    @KeithB seems like the best approach might be to return an enum with sandbox, production and unspecified. Feel free to edit the code appropriately.
  • tcurdt
    tcurdt about 7 years
    @KeithB it used to work just fine. I haven't checked in a while though. You should inspect the bundle if it doesn't work anymore and file a bug. Right now in Swift I am just checking Bundle.main.appStoreReceiptURL to know whether the app is in production or not.