Some pre-requisite information
- The sandbox environment is always used when the app is built and ran through Xcode in debug, release or profile configurations, or if the app is distributed through
development
method.
- The production environment is used when the app is distributed through
App Store Connect (App Store and TestFlight)
, Ad Hoc
and Enterprise
methods. See "Distributing Your App for Beta Testing and Releases" for more information.
- To find out what distribution mode/ APNs environment is used, you have to read the provisioning profile. On iOS, watchOS and tvOS, it is
embedded.mobileprovision
, and in macOS or Catalyst, it is embedded.provisionprofile
. You cannot read App.entitlements
, because that file isn't always available. Instead, embedded.mobileprovision
contains a dictionary (in XML format). This is an example of this file I extracted from a test app. It contains, among other things:
<key>Entitlements</key>
<dict>
<key>aps-environment</key>
<string>development</string>
...
If you generate one yourself (archive the Xcode project), you can view the package contents of the xcarchive
(/Users/username/Library/Developer/Xcode/Archives/2021-08-28/projectName\ 28-08-2021,\ 08.17.xcarchive/Products/Applications/projectName.app/embedded.mobileprovision
), and it is shown nicely in the finder preview.
There is also a comment in the Firebase iOS SDK:
* @param type The type of APNs token. Debug builds should use
* FIRMessagingAPNSTokenTypeSandbox. Alternatively, you can supply
* FIRMessagingAPNSTokenTypeUnknown to have the type automatically
* detected based on your provisioning profile.
Firebase's solution
You could read FIRMessagingTokenManager.m
, or read my analysis across different files:
In Firebase iOS SDK, If you pass don't pass a type (sandbox/ production) or explicitly pass FIRMessagingAPNSTokenTypeUnknown
, this code runs:
if (type == FIRMessagingAPNSTokenTypeUnknown) {
isSandboxApp = FIRMessagingIsSandboxApp();
}
which is
BOOL FIRMessagingIsSandboxApp(void) {
static BOOL isSandboxApp = YES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
isSandboxApp = !FIRMessagingIsProductionApp();
});
return isSandboxApp;
}
FIRMessagingIsProductionApp is a method that is 119 lines long. What does it do? It almost always defaults to a production app, theres a lot of Firebase specific configuration logic and checking if production if running on iOS simulator, if app is delivered AppStore or TestFlight
Fundamentally it checks embedded.provisionprofile
or embedded.mobileprovision
(that is how plistMap
is generated):
// plistMap is loaded from the provisioning profile in a multi step process.
NSString *apsEnvironment = [plistMap valueForKeyPath:kEntitlementsAPSEnvironmentKey];
if ([apsEnvironment isEqualToString:kAPSEnvironmentDevelopmentValue]) {
return NO;
}
where they reference the following keys inside the provisioning profile:
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
static NSString *const kEntitlementsAPSEnvironmentKey = @"Entitlements.aps-environment";
#else
static NSString *const kEntitlementsAPSEnvironmentKey =
@"Entitlements.com.apple.developer.aps-environment";
#endif
static NSString *const kAPSEnvironmentDevelopmentValue = @"development";
If you're interested in seeing how the provisioning profile is read, read the source file.
- Create full path to provisioning profile
- Load data from file path
- Clean the data (the provisioning profile contains
0
which "halts" the ASCII parser, or value larger than 127, which is invalid.)
- Convert Data to String
- Scan the string using NSScanner. They do this because the provisioning profile contains a lot more non-xml/ non-plist structure. Look at the example file.
- Convert this string back into data.
- Convert this data into a dictionary using
NSPropertyListSerialization
- Look for
ProvisionedDevices
key, if so, it's a dev profile.
- Get environment from dictionary with
kEntitlementsAPSEnvironmentKey
How does firebase servers know which endpoint to use?
Finally, once the Firebase iOS SDK knows that a device (and APNs device token) is running in production/ development, it can tell the APNs provider (their server which connects with APNs) to use the correct endpoint, either api.push.apple.com:443
and api.sandbox.push.apple.com:443
aka. api.development.push.apple.com:443
(its just a CNAME pointing to sandbox). This isProduction
or isSandbox
boolean will probably live with the APNs device token in a Firebase database.