Skip to content

Implement register() and FID-based registration with onRegistered / onUnregistered#9714

Open
zwu52 wants to merge 5 commits intofeat/messaging-api-seriesfrom
feat/messaging-phase2-concurrent-registration
Open

Implement register() and FID-based registration with onRegistered / onUnregistered#9714
zwu52 wants to merge 5 commits intofeat/messaging-api-seriesfrom
feat/messaging-phase2-concurrent-registration

Conversation

@zwu52
Copy link
Member

@zwu52 zwu52 commented Mar 11, 2026

Implements Phase 2 of FCM Web modernization: a new register() API that links the app to its Firebase Installation ID (FID) and delivers it via the onRegistered callback, plus onUnregistered for when a FID is deactivated. This supports moving from legacy FCM tokens to FID as the primary send target.

register(messaging, options?) ensures VAPID and service worker are set, gets the FID from Installations, and invokes onRegistered with it (same options as getToken). When register() is called multiple times, onRegistered is only invoked when the FID changes from the last notified value (or on first call). The “get FID → compare → notify” step is serialized so concurrent register() calls do not trigger duplicate callbacks for the same FID.

New surface: onRegistered(messaging, nextOrObserver) and onUnregistered(messaging, nextOrObserver) for subscribing to FID delivery and deactivation. Unit tests cover options passthrough, observer vs function handler, FID source, and the dedupe and three-call FID-change behavior. All 78 messaging tests pass.

@zwu52 zwu52 requested review from a team as code owners March 11, 2026 02:49
@gemini-code-assist
Copy link
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: ef850be

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

zwu52 added 2 commits March 10, 2026 19:51
- Restore copyright year to 2020; add JSDoc for FID-dedupe behavior
- Serialize get FID + compare + notify to avoid duplicate callback on concurrent register()
- Add lastNotifiedFid assertion and three-call FID-change test

Made-with: Cursor
@zwu52 zwu52 force-pushed the feat/messaging-phase2-concurrent-registration branch from 182879a to 39f546b Compare March 11, 2026 02:52
@zwu52 zwu52 requested a review from Doris-Ge March 11, 2026 17:21
@@ -0,0 +1,200 @@
/**
* @license
* Copyright 2020 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the copyright on these new files should be 2026

@zwu52 zwu52 requested a review from Doris-Ge March 19, 2026 19:07
* limitations under the License.
*/

import { SubscriptionOptions } from '../interfaces/token-details';

Choose a reason for hiding this comment

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

nit: Maybe rename this file to registration-details if easy

Copy link
Member Author

@zwu52 zwu52 Mar 20, 2026

Choose a reason for hiding this comment

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

easy with ai 🤖 done

// `FID_REGISTRATION_FAILED` to keep the error surface uniform.
// Best-effort extraction of error details; the main signal is response.ok / status.
try {
const responseData = (await response.json()) as ApiResponse;

Choose a reason for hiding this comment

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

If only this line could throw an error, can we just surround it with try-catch?

The current structure is a bit confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

refactored

getEndpoint(firebaseDependencies.appConfig),
subscribeOptions
);
} catch (err) {

Choose a reason for hiding this comment

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

Shall we retry a few times on a retriable error?

Copy link
Member Author

Choose a reason for hiding this comment

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

implemented a exponential backup for max 3 times retry.

@zwu52 zwu52 requested a review from Doris-Ge March 20, 2026 21:04
'called before calling getToken() to ensure your VAPID key is used.'
'called before calling getToken() to ensure your VAPID key is used.',
[ErrorCode.INVALID_ON_REGISTERED_HANDLER]:
'No onRegistered callback handler was provided or registered. Call onRegistered() before register().'

Choose a reason for hiding this comment

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

Suggested change
'No onRegistered callback handler was provided or registered. Call onRegistered() before register().'
'No onRegistered callback handler was provided or registered. Implement onRegistered() before register().'

Comment on lines +130 to +132
if (err instanceof Error) {
throw err;
}

Choose a reason for hiding this comment

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

Do we need this based on the above comment?

// Best-effort parse the body to extract `error.message`, but always fail with
// `FID_REGISTRATION_FAILED` to keep the error surface uniform.

* For the new FID-based register path:
* - Create (or refresh) an FCM Web registration in the backend via CreateRegistration.
* - Use the FIS auth token produced by the installations instance (implicitly associated with FID).
* - Only rely on HTTP success/failure (do not depend on returned FCM token).

Choose a reason for hiding this comment

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

nit: It's unclear to me what "returned FCM token" means. Do you mean FID?

import { arrayToBase64 } from './array-base64-translator';

// https://github.com/firebase/firebase-js-sdk/blob/7857c212f944a2a9eb421fd4cb7370181bc034b5/packages/messaging/src/interfaces/token-details.ts
// https://github.com/firebase/firebase-js-sdk/blob/7857c212f944a2a9eb421fd4cb7370181bc034b5/packages/messaging/src/interfaces/registration-details.ts

Choose a reason for hiding this comment

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

Revert this change as the updated link is invalid. Same for the following changes.

Comment on lines +45 to +55
if (!navigator) {
throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW);
}

if (Notification.permission === 'default') {
await Notification.requestPermission();
}

if (Notification.permission !== 'granted') {
throw ERROR_FACTORY.create(ErrorCode.PERMISSION_BLOCKED);
}

Choose a reason for hiding this comment

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

Do we need unit tests to cover these logics?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants