4

In Apple Push Notification service (APNs), the server-side developer must choose the environment type (sandbox or production) as the HTTP/2 URL (api.sandbox.push.apple.com or api.push.apple.com). [1]

On the other hand, in Firebase Cloud Messaging (FCM) over APNs, there seems no explicit interface to specify the environment type. [2]

So I guess FCM somehow decide the environment type internally, but I have no idea about how it detects the environment type.

Does anyone have knowledge about it? Any insight would be helpful. Thanks!

Yuki Hashimoto
  • 1,013
  • 7
  • 19
  • Thats also my conclusion. Maybe when the tokens are registered the firebase sdk detects the build mode! debug or release. Or maybe there is some pattern in the tokens that allows to distinguish between production and development tokens. – hasan Apr 02 '21 at 04:02
  • I appreciate your kind reply! Hmm... do you know whether there is any documentation or inference about it? – Yuki Hashimoto Apr 03 '21 at 12:36
  • No. I didn't bother because everything is working :D so move on. haha – hasan Apr 03 '21 at 13:23

3 Answers3

4

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.

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
1

I found the answer on the doc on FIRInstanceIDAPNSTokenType

http://cocoadocs.org/docsets/FirebaseInstanceID/1.0.6/Constants/FIRInstanceIDAPNSTokenType.html

The APNS token type for the app. If the token type is set to UNKNOWN InstanceID will implicitly try to figure out what the actual token type is from the provisioning profile.

Therefore, the answer is "the actual token type is determined by the provisioning profile", maybe by "aps-environment" key.

Yuki Hashimoto
  • 1,013
  • 7
  • 19
  • 1
    Found the firebase doc regarding this. [link](https://firebase.google.com/docs/reference/ios/firebasemessaging/api/reference/Enums/FIRMessagingAPNSTokenType) – Prasad Parab Apr 09 '21 at 07:40
0

The FCM token we use for sending the notifications is generated by the SDK using multiple parameters. It contains information about the actual APNS device token, Project ID, Project URL endpoint on Firebase, App Bundle Identifier, etc (You get an invalid token error if you try to use some other token generated from different Firebase configuration). It stands to reason that the FCM token also takes into account the Environment used for compiling the application (DEBUG/RELEASE). Using this information, Firebase should be able to use the corresponding APNS gateways.

Prasad Parab
  • 381
  • 1
  • 12