Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
12a6ad8
Introduce new Info.plist key `FirebaseMessagingInstallationIdEnabled`.
leojaygoogle Apr 17, 2026
c3c3d67
Implement the FCM FID registration operation in the Messaging token m…
leojaygoogle Apr 26, 2026
b442d42
Add retry logic on network error and backend 5xx error.
leojaygoogle Apr 26, 2026
fe49a75
Implement the FCM FID unregistration operation.
leojaygoogle Apr 27, 2026
f1294be
Add didReceiveRegistration delegate method.
leojaygoogle Apr 28, 2026
8c0ba25
Update the sample app to handle the new registration ID.
leojaygoogle Apr 28, 2026
b471f82
Update CHANGELOG
leojaygoogle Apr 29, 2026
4b72a42
Style update.
leojaygoogle Apr 29, 2026
c28628b
Use the internal FirebaseInstallations header file.
leojaygoogle Apr 29, 2026
e7aeac8
Rename `registrationId` to `installationId` in messaging delegate met…
leojaygoogle Apr 29, 2026
836b697
Update copyright header to include LLC
leojaygoogle Apr 29, 2026
dff8587
Fix various compile warnings.
leojaygoogle Apr 29, 2026
503c5b6
Add an FIRMessagingErrorCode to indicate installation related errors.
leojaygoogle May 1, 2026
c12d985
Move heartbeatLogger from FIRMessagingTokenOperation to FIRMessagingT…
leojaygoogle May 1, 2026
1d4c3f8
Fix style warnings and address review comments.
leojaygoogle May 4, 2026
e984467
Mark `authorizedEntity` as `nullable`.
leojaygoogle May 4, 2026
125dac3
Add FIRMessagingInstallationIdUnregisteredNotification and make sure …
leojaygoogle May 5, 2026
d95a204
Monitor FID change events and register the new FID when it happens.
leojaygoogle May 6, 2026
f7cea1d
Delete unused methods.
leojaygoogle May 6, 2026
fc874aa
Add test `testRegisterNotifiesDelegateWhenInstallationIdEnabled`.
leojaygoogle May 13, 2026
26c94d2
Add `testUnregisterNotifiesDelegateWhenInstallationIdEnabled`
leojaygoogle May 13, 2026
6093f94
Move installationIDObserver setup to the `start` method, instead of `…
leojaygoogle May 13, 2026
e7433b8
Fix test ordering failure in FIRMessagingTokenInfoTest.
leojaygoogle May 13, 2026
77ad7f5
Add test `testAppStartNotifiesDelegateWhenBothAutoInitAndInstallation…
leojaygoogle May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions FirebaseMessaging/Apps/Shared/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,21 @@
return true
}

func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("APNs Device Token: \(tokenString)")
if Messaging.messaging().isInstallationIdEnabled {
Messaging.messaging().register()
}
}

// Implement this to display notification when app is in foreground.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
-> Void) {
completionHandler([.alert, .sound])

Check warning on line 56 in FirebaseMessaging/Apps/Shared/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / sample-build-test (MessagingSample, iOS) / build

'alert' was deprecated in iOS 14.0
}

func userNotificationCenter(_ center: UNUserNotificationCenter,
Expand Down
37 changes: 24 additions & 13 deletions FirebaseMessaging/Apps/Shared/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,24 +148,34 @@ struct ContentView: View {
}

func getFCMToken() {
Messaging.messaging().token { token, error in
guard let token = token, error == nil else {
self.log = "Failed getting iid and token: \(String(describing: error))"
return
if Messaging.messaging().isInstallationIdEnabled {
Messaging.messaging().register()
log = "Called register()"
} else {
Messaging.messaging().token { token, error in
guard let token = token, error == nil else {
self.log = "Failed getting iid and token: \(String(describing: error))"
return
}
self.identity.token = token
self.log = "Successfully got token."
print("Token: ", self.identity.token ?? "")
}
self.identity.token = token
self.log = "Successfully got token."
print("Token: ", self.identity.token ?? "")
}
}

func deleteFCMToken() {
Messaging.messaging().deleteToken { error in
if let error = error as NSError? {
self.log = "Failed deleting token: \(error)"
return
if Messaging.messaging().isInstallationIdEnabled {
Messaging.messaging().unregister()
log = "Called unregister()"
} else {
Messaging.messaging().deleteToken { error in
if let error = error as NSError? {
self.log = "Failed deleting token: \(error)"
return
}
self.log = "Successfully deleted token."
}
self.log = "Successfully deleted token."
}
}

Expand Down Expand Up @@ -210,6 +220,7 @@ struct ActivityViewController: UIViewControllerRepresentable {
}

struct SettingsView: View {
@EnvironmentObject var identity: Identity
@EnvironmentObject var settings: UserSettings
@State var shouldUseDelegate = true
@State private var isSharePresented: Bool = false
Expand All @@ -235,7 +246,7 @@ struct SettingsView: View {
.sheet(isPresented: $isSharePresented, onDismiss: {
print("Dismiss")
}, content: {
let items = [Messaging.messaging().fcmToken]
let items = [identity.token ?? "None"]
ActivityViewController(activityItems: items as [Any])
})
}
Expand Down
7 changes: 7 additions & 0 deletions FirebaseMessaging/Apps/Shared/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, MessagingDelegate {
print("Did refresh token:\n", identity.token ?? "")
print("\n=============================\n")
}

func messaging(_ messaging: Messaging, didReceiveRegistration installationId: String?) {
identity.token = installationId
print("=============================")
print("Did refresh FCM installation ID: \(String(describing: installationId))")
print("=============================")
}
}
3 changes: 3 additions & 0 deletions FirebaseMessaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [added] Add support for the new FCM registration ID.

# 12.8.0
- [fixed] Fix missing database crash on launch. (#14880)

Expand Down
117 changes: 104 additions & 13 deletions FirebaseMessaging/Sources/FIRMessaging.m
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update FIRMessagingTest.m to test changes in this file? Some test cases to consider:

  • When isInstallationIdEnabled is true

    1. A call to register() can successfully invoke didReceiveRegistration() regardless whether the same FID has been registered or not and regardless whether isAutoInit is enabled or not.
    2. A call to unregister() should invoke didUnregister() and leave the app as unregistered.
    3. When isAutoInit=true, didReceiveRegistration() is invoked on app start when a new FID is registered or the registered FID has expired or the FID is changed.
    4. token/deleteToken should throw an error
    5. retrieveFCMTokenForSenderID/deleteFCMTokenForSenderID should throw an error
  • When isInstallationIdEnabled is false

    1. register()/unregister() should throw an error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add tests for the first 3 items. Regarding throwing exceptions, our style guide suggests avoiding them. go/objc-style#avoid-throwing-exceptions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! Good to know that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
NSString *const kFIRMessagingPlistAutoInitEnabled =
@"FirebaseMessagingAutoInitEnabled"; // Auto Init Enabled key stored in Info.plist

NSString *const kFIRMessagingPlistInstallationIdEnabled =
@"FirebaseMessagingInstallationIdEnabled"; // Installation ID Enabled key stored in Info.plist

NSString *const FIRMessagingErrorDomain = @"com.google.fcm";

BOOL FIRMessagingIsAPNSSyncMessage(NSDictionary *message) {
Expand Down Expand Up @@ -232,7 +235,7 @@
// happens before developers able to set the delegate
// Hence first token set must be happen here after listener is set
// TODO(chliangGoogle) Need to investigate better solution.
[self updateDefaultFCMToken:self.FCMToken];
[self updateDefaultFCMToken:[self.tokenManager tokenAndRequestIfNotExist]];
}];
} else if (self.isAutoInitEnabled && self.APNSToken) {
// When there is no cached token, must check auto init is enabled.
Expand Down Expand Up @@ -493,7 +496,19 @@
}
}

- (BOOL)isInstallationIdEnabled {
id isInstallationIdEnabledObject =
[[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRMessagingPlistInstallationIdEnabled];
return [isInstallationIdEnabledObject boolValue];
}

- (NSString *)FCMToken {
if (self.isInstallationIdEnabled) {
FIRMessagingLoggerDebug(
kFIRMessagingMessageCodeInstallationIdEnabled,
@"FirebaseMessagingInstallationIdEnabled is set to YES, token is not available.");
return nil;
}
// Gets the current default token, and requests a new one if it doesn't exist.
NSString *token = [self.tokenManager tokenAndRequestIfNotExist];
return token;
Expand Down Expand Up @@ -617,14 +632,27 @@
// NOTE: Once |didReceiveRegistrationToken:| can be made a required method, this
// check can be removed.
- (void)validateDelegateConformsToTokenAvailabilityMethods {
if (self.delegate &&
![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented,
@"The object %@ does not respond to "
@"-messaging:didReceiveRegistrationToken:. Please implement "
@"-messaging:didReceiveRegistrationToken: to be provided with an FCM "
@"token.",
self.delegate.description);
if (self.delegate) {
if ([self isInstallationIdEnabled]) {
if (![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistration:)]) {
FIRMessagingLoggerWarn(
kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented,
@"The object %@ does not respond to "
@"-messaging:didReceiveRegistration:. Please implement "
@"-messaging:didReceiveRegistration: to be provided with an installation ID.",
self.delegate.description);
}
} else {
if (![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
FIRMessagingLoggerWarn(
kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented,
@"The object %@ does not respond to "
@"-messaging:didReceiveRegistrationToken:. Please implement "
@"-messaging:didReceiveRegistrationToken: to be provided with an FCM "
@"token.",
self.delegate.description);
}
}
}
}

Expand All @@ -636,8 +664,14 @@
});
return;
}
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
[self.delegate messaging:self didReceiveRegistrationToken:self.tokenManager.defaultFCMToken];
if ([self isInstallationIdEnabled]) {
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistration:)]) {
[self.delegate messaging:self didReceiveRegistration:self.tokenManager.defaultFCMToken];
}
} else {
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
[self.delegate messaging:self didReceiveRegistrationToken:self.tokenManager.defaultFCMToken];
}
}

// Should always trigger the token refresh notification when the delegate method is called
Expand All @@ -646,6 +680,57 @@
object:self.tokenManager.defaultFCMToken];
}

#pragma mark - FID

- (void)register {
if (!self.isInstallationIdEnabled) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeInstallationIdDisabled,
@"FirebaseMessagingInstallationIdEnabled is not set to YES, so "
@"FID operations are not supported.");
return;
}
if (!FIRApp.defaultApp.options.GCMSenderID.length) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenFetch,
@"No Sender ID is available to register");
return;
}
[self.tokenManager tokenAndRequestIfNotExist];
}

- (void)unregister {
if (!self.isInstallationIdEnabled) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeInstallationIdDisabled,
@"FirebaseMessagingInstallationIdEnabled is not set to YES, so "
@"FID operations are not supported.");
return;
}
NSString *senderID = FIRApp.defaultApp.options.GCMSenderID;
if (!senderID.length) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenDelete,
@"No Sender ID is available to unregister");
return;
}

FIRMessaging_WEAKIFY(self);
[self.installations installationIDWithCompletion:^(NSString *_Nullable identifier,
NSError *_Nullable error) {
FIRMessaging_STRONGIFY(self);
if (error) {
FIRMessagingLoggerError(kFIRMessagingErrorCodeInvalidIdentity,

Check warning on line 719 in FirebaseMessaging/Sources/FIRMessaging.m

View workflow job for this annotation

GitHub Actions / messaging-integration-tests / build

implicit conversion from enumeration type 'enum FIRMessagingErrorCode' to different enumeration type 'FIRMessagingMessageCode' (aka 'enum FIRMessagingMessageCode') [-Wimplicit-enum-enum-cast]

Check warning on line 719 in FirebaseMessaging/Sources/FIRMessaging.m

View workflow job for this annotation

GitHub Actions / sample-build-test (MessagingSample, iOS) / build

implicit conversion from enumeration type 'enum FIRMessagingErrorCode' to different enumeration type 'FIRMessagingMessageCode' (aka 'enum FIRMessagingMessageCode') [-Wimplicit-enum-enum-cast]

Check warning on line 719 in FirebaseMessaging/Sources/FIRMessaging.m

View workflow job for this annotation

GitHub Actions / sample-build-test (SwiftUISample, iOS) / build

implicit conversion from enumeration type 'enum FIRMessagingErrorCode' to different enumeration type 'FIRMessagingMessageCode' (aka 'enum FIRMessagingMessageCode') [-Wimplicit-enum-enum-cast]
@"Failed to get installation ID.");
} else {
[self.tokenManager deleteTokenWithAuthorizedEntity:senderID
scope:kFIRMessagingDefaultTokenScope
instanceID:identifier
handler:^(NSError *_Nullable error) {
if (!error && [self isAutoInitEnabled]) {
[self.tokenManager tokenAndRequestIfNotExist];
}
Comment thread
leojaygoogle marked this conversation as resolved.
Outdated
}];
}
}];
}

#pragma mark - Topics

+ (NSString *)normalizeTopic:(NSString *)topic {
Expand Down Expand Up @@ -814,8 +899,14 @@
});
return;
}
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
[self.delegate messaging:self didReceiveRegistrationToken:self.tokenManager.defaultFCMToken];
if ([self isInstallationIdEnabled]) {
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistration:)]) {
[self.delegate messaging:self didReceiveRegistration:self.tokenManager.defaultFCMToken];
}
} else {
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
[self.delegate messaging:self didReceiveRegistrationToken:self.tokenManager.defaultFCMToken];
}
}
// Should always trigger the token refresh notification when the delegate method is called
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
Expand Down
3 changes: 3 additions & 0 deletions FirebaseMessaging/Sources/FIRMessagingCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) {
kFIRMessagingMessageCodeTopicFormatIsDeprecated = 2024,
kFIRMessagingMessageCodeDirectChannelConnectionFailed = 2025,
kFIRMessagingMessageCodeInvalidClient = 2026, // no longer used
kFIRMessagingMessageCodeInstallationIdEnabled = 2027,
kFIRMessagingMessageCodeInstallationIdDisabled = 2028,
kFIRMessagingMessageCodeDebug = 2029,

// DO NOT USE 4000, 4004 - 4013
kFIRMessagingMessageCodeClient001 = 4001, // I-FCM004000
Expand Down
1 change: 1 addition & 0 deletions FirebaseMessaging/Sources/FIRMessaging_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) {
};

FOUNDATION_EXPORT NSString *const kFIRMessagingPlistAutoInitEnabled;
FOUNDATION_EXPORT NSString *const kFIRMessagingPlistInstallationIdEnabled;
FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyUseMessagingDelegate;
FOUNDATION_EXPORT NSString *const kFIRMessagingPlistUseMessagingDelegate;
Expand Down
Comment thread
leojaygoogle marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ NS_SWIFT_NAME(MessagingDelegate)
- (void)messaging:(FIRMessaging *)messaging
didReceiveRegistrationToken:(nullable NSString *)fcmToken
NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:));

/// This method is similar to `messaging(_:didReceiveRegistrationToken:)` above, but will be called
/// instead when `isInstallationIdEnabled` is `YES`.
- (void)messaging:(FIRMessaging *)messaging
didReceiveRegistration:(nullable NSString *)installationId
NS_SWIFT_NAME(messaging(_:didReceiveRegistration:));
@end

/**
Expand Down Expand Up @@ -238,6 +244,20 @@ NS_SWIFT_NAME(Messaging)
*/
@property(nonatomic, assign, getter=isAutoInitEnabled) BOOL autoInitEnabled;

/**
* Is Firebase Messaging installation ID enabled? It's the `FirebaseMessagingInstallationIdEnabled`
Comment thread
leojaygoogle marked this conversation as resolved.
Outdated
* property in `Info.plist` file. The default value is `NO`.
*
* When enabled, there are several behavior changes:
*
* 1. An installation ID, instead of an FCM Registration token, is generated.
Comment thread
leojaygoogle marked this conversation as resolved.
Outdated
* 2. All token related operations like `tokenWithCompletion`, `deleteTokenWithCompletion`,
* `retrieveFCMTokenForSenderID`, and `deleteFCMTokenForSenderID` will always fail
* with an error indicating that the operation is not supported.
* 3. You should call `register()` and `unregister()` instead.
*/
@property(nonatomic, readonly, getter=isInstallationIdEnabled) BOOL installationIdEnabled;

/**
* The FCM registration token is used to identify this device so that FCM can send notifications to
* it. It is associated with your APNs token when the APNs token is supplied, so messages sent to
Expand Down Expand Up @@ -314,6 +334,25 @@ NS_SWIFT_NAME(Messaging)
completion:(void (^)(NSError *_Nullable error))completion
NS_SWIFT_NAME(deleteFCMToken(forSenderID:completion:));

#pragma mark - FID

/**
* Asynchronously registers to the FCM backend.
*
* Please use the FIRMessaging delegate method
* `messaging:didReceiveRegistration:` to receive the registered FID.
Comment thread
leojaygoogle marked this conversation as resolved.
Outdated
*
* This method works only when `FirebaseMessaging.isInstallationIdEnabled` is set to `YES`.
*/
- (void)register;

/**
* Asynchronously unregisters from the FCM backend.
*
* This method works only when `FirebaseMessaging.isInstallationIdEnabled` is set to `YES`.
*/
- (void)unregister;
Comment thread
leojaygoogle marked this conversation as resolved.

#pragma mark - Topics

/**
Expand Down
34 changes: 34 additions & 0 deletions FirebaseMessaging/Sources/Token/FIRMessagingFIDRegisterOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenOperation.h"

@class FIRInstallations;

NS_ASSUME_NONNULL_BEGIN

@interface FIRMessagingFIDRegisterOperation : FIRMessagingTokenOperation

- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
scope:(NSString *)scope
options:(nullable NSDictionary<NSString *, NSString *> *)options
instanceID:(NSString *)instanceID
heartbeatLogger:(id<FIRHeartbeatLoggerProtocol>)heartbeatLogger
installations:(FIRInstallations *)installations;

@end

NS_ASSUME_NONNULL_END
Loading
Loading