Firebase Cloud Messaging (FCM) 如何切换 Apple Push Notification 服务 (APNs) 的环境?

How does Firebase Cloud Messaging (FCM) switch the environment of Apple Push Notification service (APNs)?

在 Apple 推送通知服务 (APNs) 中,服务器端开发人员必须选择环境类型(沙盒或生产)作为 HTTP/2 URL(api.sandbox.push.apple.com 或 api.push.apple.com). [1]

另一方面,在基于 APN 的 Firebase 云消息传递 (FCM) 中,似乎没有明确的接口来指定环境类型。 [2]

所以我猜想 FCM 以某种方式在内部决定了环境类型,但我不知道它是如何检测环境类型的。

有人知道吗?任何见解都会有所帮助。谢谢!

我在 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.

因此,答案是“实际令牌类型由配置文件决定”,可能由“aps-environment”键决定。

我们用于发送通知的 FCM 令牌是由 SDK 使用多个参数生成的。它包含有关实际 APNS 设备令牌、项目 ID、Firebase 上的项目 URL 端点、App Bundle 标识符等的信息(如果您尝试使用从不同 Firebase 配置生成的其他令牌,则会收到无效令牌错误)。按理说,FCM 令牌还考虑了用于编译应用程序的环境 (DEBUG/RELEASE)。使用此信息,Firebase 应该能够使用相应的 APNS 网关。

一些先决条件信息

  • 构建应用程序时始终使用沙箱环境,在调试、发布或配置文件配置中 运行 到 Xcode, 如果应用程序通过 development 方法分发。
  • 应用程序通过App Store Connect (App Store and TestFlight)Ad HocEnterprise方法分发时使用生产环境。有关详细信息,请参阅 "Distributing Your App for Beta Testing and Releases"
  • 要找出使用的分发模式/APNs 环境,您必须阅读配置文件。在 iOS、watchOS 和 tvOS 上,它是 embedded.mobileprovision,在 macOS 或 Catalyst 上,它是 embedded.provisionprofile。您无法读取 App.entitlements,因为该文件 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。其中包括:
<key>Entitlements</key>
<dict>
<key>aps-environment</key>
<string>development</string>
...

如果你自己生成一个(存档Xcode项目),你可以查看xcarchive/Users/username/Library/Developer/Xcode/Archives/2021-08-28/projectName\ 28-08-2021,\ 08.17.xcarchive/Products/Applications/projectName.app/embedded.mobileprovision)的包内容,并且在finder中很好地显示预览。

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 的解决方案

您可以阅读 FIRMessagingTokenManager.m,或阅读我对不同文件的分析:

Firebase iOS SDK 中,如果您不传递类型(沙盒/生产)或显式传递 FIRMessagingAPNSTokenTypeUnknown,此代码将运行:

  if (type == FIRMessagingAPNSTokenTypeUnknown) {
    isSandboxApp = FIRMessagingIsSandboxApp();
  }

这是

BOOL FIRMessagingIsSandboxApp(void) {
  static BOOL isSandboxApp = YES;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    isSandboxApp = !FIRMessagingIsProductionApp();
  });
  return isSandboxApp;
}

FIRMessagingIsProductionApp 是一个 119 行长的方法。它有什么作用?它几乎总是默认为生产应用程序,有很多 Firebase 特定的配置逻辑并检查生产是否 运行 在 iOS 模拟器上,如果应用程序交付 AppStore 或 TestFlight

它从根本上检查 embedded.provisionprofileembedded.mobileprovision(这就是 plistMap 的生成方式):

// plistMap is loaded from the provisioning profile in a multi step process.
NSString *apsEnvironment = [plistMap valueForKeyPath:kEntitlementsAPSEnvironmentKey];

  if ([apsEnvironment isEqualToString:kAPSEnvironmentDevelopmentValue]) {
    return NO;
  }

他们在配置文件中引用了以下键:

#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";

如果您有兴趣了解配置文件的读取方式,请阅读源文件。

  • 创建配置文件的完整路径
  • Load data 来自文件路径
  • 清理数据(供应配置文件包含 0 会“停止”ASCII 解析器,或者大于 127 的值无效。)
  • 将数据转换为字符串
  • 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 文件。
  • 将此字符串转换回数据。
  • 使用 NSPropertyListSerialization
  • 将此数据转换成字典
  • 查找 ProvisionedDevices 密钥,如果是,则它是一个开发配置文件。
  • 使用 kEntitlementsAPSEnvironmentKey
  • 从字典中获取环境

firebase 服务器如何知道要使用哪个端点?

最后,一旦 Firebase iOS SDK 知道设备(和 APNs 设备令牌)在生产/开发中 运行,它可以告诉 APNs 提供商(他们的服务器与 APNs 连接) 使用正确的端点,也就是 api.push.apple.com:443api.sandbox.push.apple.com:443api.development.push.apple.com:443(它只是一个指向沙箱的 CNAME)。此 isProductionisSandbox 布尔值可能与 Firebase 数据库中的 APNs 设备令牌一起存在。