Skip to content

Commit 97f61a4

Browse files
build: require firebase ^12.18.0 and wrap the functions it adds (#3761)
The generated src/<module>/firebase.ts files are written from the type declarations of the installed firebase, which decides which functions AngularFire wraps. That was 12.4.0 while npm latest reached 12.18.0, leaving everything firebase added in between to reach callers through `export *`, with no zone integration and no pending-task registration. Raising the required version wraps them. Everything the override entries in #3759 classified as unwrapped stays out. `getImagenModel` loses its entry because firebase removed the symbol in 12.18.0, and the exemption list it sat in is empty now and stays as the place the next unclassified name goes. Only `src/messaging/firebase.ts` changes among the generated files, gaining onRegistered, onUnregistered, register and unregister. `docs/messaging.md` was updated to use those calls. The Node send example keeps its token field, which Firebase says still accepts an installation ID during the migration, rather than the fid field it recommends, because firebase-admin 13.5.0 does not declare one. `exportsSeenPerOverrides` is keyed by the overrides object, and firestore and firestore/lite are handed the same firestoreOverrides, so a name listed for one silences the check for the other. Fixes #3756
1 parent e578ccb commit 97f61a4

11 files changed

Lines changed: 742 additions & 635 deletions

File tree

docs/messaging.md

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# Cloud Messaging
88

9-
Firebase FCM allows you to register devices with unique FCM tokens, that you can later programtically send notifications to using Firebase Cloud Functions. It is up to the application to update these tokens in Firebase if you want to use them in other layers of your application, i.e send a notification to all administrators, etc. In that case, you would likely want to store your fcm tokens on your user collection, or a sub collection or another collection with different permissions.
9+
Firebase Cloud Messaging (FCM) allows you to register devices with unique FCM tokens, that you can later programatically send notifications to using Firebase Cloud Functions. It is up to the application to update these tokens in Firebase if you want to use them in other layers of your application, i.e send a notification to all administrators, etc. In that case, you would likely want to store your fcm tokens on your user collection, or a sub collection or another collection with different permissions.
1010

1111
## Dependency Injection
1212

@@ -47,19 +47,20 @@ export class AppComponent {
4747

4848
# Create a Firebase Messaging Service Worker
4949

50-
There are two parts to Firebase Messaging, a Service Worker and the DOM API. Angular Fire Messaging allows you to request permission, get tokens, delete tokens, and subscribe to messages on the DOM side. To register to receive notifications you need to set up the Service Worker. [The official Firebase documentation for setting up the details exactly how to do that](https://firebase.google.com/docs/cloud-messaging/js/client).
50+
There are two parts to Firebase Messaging, a Service Worker and the DOM API. Angular Fire Messaging allows you to request permission, register this app instance, observe when it is registered or unregistered, and subscribe to messages on the DOM side. To register to receive notifications you need to set up the Service Worker. [The official Firebase documentation for setting up the details exactly how to do that](https://firebase.google.com/docs/cloud-messaging/js/client).
5151

5252
#### Create your firebase-messaging-sw.js file in your src/assets folder
5353

5454
*Note: When copying the below file, make sure your firebase version in your installation matches the version your are importing from below*
5555

5656
It may be wise to use file replacements or environments here for different environments
5757

58-
```
59-
// This sample application is using 12.4.0, make sure you are importing the same version
58+
```js
59+
/* Replace <firebase-version> with the firebase version in your package.json. The service
60+
* worker and your application have to load the same version. */
6061

61-
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.4.0/firebase-app.js";
62-
import { getMessaging } from "https://www.gstatic.com/firebasejs/12.4.0/firebase-messaging-sw.js";
62+
import { initializeApp } from "https://www.gstatic.com/firebasejs/<firebase-version>/firebase-app.js";
63+
import { getMessaging } from "https://www.gstatic.com/firebasejs/<firebase-version>/firebase-messaging-sw.js";
6364

6465
const firebaseApp = initializeApp({
6566
apiKey: "",
@@ -73,54 +74,81 @@ const firebaseApp = initializeApp({
7374
const messaging = getMessaging(firebaseApp);
7475
```
7576

77+
# Registering this app instance
78+
79+
Firebase deprecated `getToken` and `deleteToken` in firebase 12.18 and will remove them. Use `register` with `onRegistered` in place of `getToken`, and `unregister` with `onUnregistered` in place of `deleteToken`. [Firebase's client guide](https://firebase.google.com/docs/cloud-messaging/js/client) describes the model and asks that you not mix the two sets.
80+
81+
Three things to know before you copy the example below:
82+
83+
- `onRegistered` has to be listening before `register` runs, which is why the example subscribes first. Otherwise `register` throws `No onRegistered callback handler was provided or registered.`
84+
- **Ask for notification permission yourself before calling `register`,** as the example does. Otherwise `register` asks for you from inside a call AngularFire wraps, which holds the client app unstable until the dialog is dismissed.
85+
- That delays Angular event replay and clearing the server-rendered DOM, and logs a development-only warning after ten seconds. Asking first avoids all of it.
86+
- This behavior may change in a future release.
87+
- The identifier reaches you through a callback rather than as a return value, and again whenever it changes, so store it from inside the callback rather than once at startup.
88+
7689
# Example messaging service
7790

78-
```
79-
import { Injectable } from "@angular/core";
80-
import { Messaging, MessagePayload, getToken, onMessage, deleteToken } from "@angular/fire/messaging";
91+
```ts
92+
import { EnvironmentInjector, Injectable, inject, runInInjectionContext } from "@angular/core";
93+
import { Messaging, MessagePayload, onMessage, onRegistered, onUnregistered, register, unregister } from "@angular/fire/messaging";
8194
import { Observable, tap } from "rxjs";
8295

83-
@Injectable({
84-
providedIn: "root",
85-
})
96+
@Injectable({ providedIn: "root" })
8697
export class FcmService {
87-
message$: Observable<MessagePayload>;
98+
private readonly injector = inject(EnvironmentInjector);
99+
/* `onMessage` converted to an observable. Returns the unsubscribe function returned by
100+
* `onMessage` to stop the listener when the last subscriber unsubscribes. */
101+
message$ = new Observable<MessagePayload>(
102+
subscriber => onMessage(this.msg, (msg) => subscriber.next(msg))
103+
).pipe(tap((msg) => console.log("My Firebase Cloud Message", msg)));
88104

89105
constructor(private msg: Messaging) {
90-
Notification.requestPermission().then(
91-
(notificationPermissions: NotificationPermission) => {
92-
if (notificationPermissions === "granted") {
93-
console.log("Granted");
94-
}
95-
if (notificationPermissions === "denied") {
96-
console.log("Denied");
97-
}
98-
});
99-
navigator.serviceWorker
100-
.register("/assets/firebase-messaging-sw.js", {
101-
type: "module",
102-
})
103-
.then((serviceWorkerRegistration) => {
104-
getToken(this.msg, {
105-
vapidKey: `an optional key generated on Firebase for your fcm tokens`,
106-
serviceWorkerRegistration: serviceWorkerRegistration,
107-
}).then((token) => {
108-
console.log('my fcm token', token);
109-
// This is a good place to then store it on your database for each user
110-
});
111-
});
112-
this.message$ = new Observable<MessagePayload>((sub) =>
113-
onMessage(this.msg, (msg) => sub.next(msg))).pipe(
114-
tap((msg) => {
115-
console.log("My Firebase Cloud Message", msg);
116-
})
106+
// Set listeners before calling `register` to avoid throwing.
107+
this.listenForRegistrationChanges();
108+
this.registerForMessages();
109+
}
110+
111+
private listenForRegistrationChanges() {
112+
onRegistered(this.msg, (installationId) => {
113+
/* This is a good place to store it in your database for each user.
114+
* This callback fires whenever `installationId` changes. */
115+
console.log("my installation id", installationId);
116+
});
117+
118+
onUnregistered(this.msg, (installationId) => {
119+
// Drop it from your database. Sending messages to an unregistered ID results in a 404.
120+
console.log("no longer registered", installationId);
121+
});
122+
}
123+
124+
private async registerForMessages() {
125+
/* Request notification permission before calling `register`, otherwise
126+
* `register` holds the app unstable until the user answers. */
127+
if (
128+
Notification.permission === "default" &&
129+
await Notification.requestPermission() !== "granted"
130+
) {
131+
return;
132+
}
133+
134+
// Register the service worker.
135+
const serviceWorkerRegistration = await navigator.serviceWorker
136+
.register("/assets/firebase-messaging-sw.js", { type: "module" });
137+
138+
/* Run `register` inside an injection context. Outside one AngularFire cannot wrap it, and
139+
* warns. See `zones.md` for what wrapping adds. */
140+
runInInjectionContext(this.injector, () =>
141+
register(this.msg, {
142+
vapidKey: `an optional public VAPID key you generate for your Firebase project`,
143+
serviceWorkerRegistration,
144+
}).catch((error) => console.error("could not register for messages", error))
117145
);
118146
}
119147

120-
async deleteToken() {
121-
// We can also delete fcm tokens, make sure to also update this on your firestore db if you are storing them as well
122-
// This calls the imported deleteToken, not this method. Class methods are not in lexical scope
123-
await deleteToken(this.msg);
148+
// Called from your app, for example when a user turns notifications off.
149+
async unregister() {
150+
// This calls the imported unregister, not this method. Class methods are not in lexical scope.
151+
await unregister(this.msg);
124152
}
125153
}
126154
```
@@ -129,9 +157,9 @@ export class FcmService {
129157

130158
Firebase will allow you to send a test notification under Engage > Messaging > New Campaign > Notifications. Here you can click send a test message. Additionally, you can send them programmatically through Firebase cloud functions.
131159

132-
Here is a barebones Node example:
160+
Here is a barebones Node example. Its `token` field still accepts a Firebase Installation ID during the migration, so it works whether you registered with `getToken` or with `register`. Firebase's [Admin SDK send guide](https://firebase.google.com/docs/cloud-messaging/send/admin-sdk) documents a dedicated `fid` field to move to.
133161

134-
```
162+
```ts
135163
export const sendTestMessage = onRequest(async (_, res) => {
136164
try {
137165
const message = {
@@ -152,7 +180,7 @@ export const sendTestMessage = onRequest(async (_, res) => {
152180

153181
Here is a Node example that listens for a new comment on a collection, then sends a notification, and also adds it to a cache on Firebase so users can click through them.
154182

155-
```
183+
```ts
156184
exports.onPostReply =
157185
onDocumentCreated("comments/{commentId}", async (event) => {
158186
if (!event) throw new Error("No event found for document creation");
@@ -227,4 +255,5 @@ async function createNotificationAndCache(
227255
firestore.collection("notificationCache").add(notificationCacheValue));
228256

229257
await Promise.all(promises);
230-
} ```
258+
}
259+
```

docs/version-21-upgrade.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ ng update @angular/fire # then AngularFire 21
1111

1212
`ng update @angular/fire` runs a migration that:
1313

14-
- **Aligns your `firebase` dependency to `^12.4.0`.** AngularFire 21 requires Firebase JS SDK 12. If your app still requested `firebase` 11, npm would install both 11 and 12 side by side, and the two copies reject each other's objects at runtime. The migration updates the dependency and reinstalls so you end up with a single copy. Verify with `npm ls firebase`.
14+
- **Aligns your `firebase` dependency to `^12.18.0`.** AngularFire 21 requires Firebase JS SDK 12, at 12.18 or later. If your app requested an older `firebase`, whether that is 11 or an earlier 12, npm would install both that copy and the one AngularFire needs side by side, and the two copies reject each other's objects at runtime. The migration updates the dependency and reinstalls so you end up with a single copy. Verify with `npm ls firebase`.
1515
- **Rewrites Vertex AI imports to AI Logic** (see below).
1616

1717
## Vertex AI is now Firebase AI Logic

0 commit comments

Comments
 (0)