Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
125 changes: 77 additions & 48 deletions docs/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# Cloud Messaging

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

## Dependency Injection

Expand Down Expand Up @@ -47,19 +47,20 @@ export class AppComponent {

# Create a Firebase Messaging Service Worker

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).
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).

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

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

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

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

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

const firebaseApp = initializeApp({
apiKey: "",
Expand All @@ -73,54 +74,81 @@ const firebaseApp = initializeApp({
const messaging = getMessaging(firebaseApp);
```

# Registering this app instance

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.

Three things to know before you copy the example below:

- `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.`
- **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.
- 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.
- This behavior may change in a future release.
- 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.

# Example messaging service

```
import { Injectable } from "@angular/core";
import { Messaging, MessagePayload, getToken, onMessage, deleteToken } from "@angular/fire/messaging";
```ts
import { EnvironmentInjector, Injectable, inject, runInInjectionContext } from "@angular/core";
import { Messaging, MessagePayload, onMessage, onRegistered, onUnregistered, register, unregister } from "@angular/fire/messaging";
import { Observable, tap } from "rxjs";

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

constructor(private msg: Messaging) {
Notification.requestPermission().then(
(notificationPermissions: NotificationPermission) => {
if (notificationPermissions === "granted") {
console.log("Granted");
}
if (notificationPermissions === "denied") {
console.log("Denied");
}
});
navigator.serviceWorker
.register("/assets/firebase-messaging-sw.js", {
type: "module",
})
.then((serviceWorkerRegistration) => {
getToken(this.msg, {
vapidKey: `an optional key generated on Firebase for your fcm tokens`,
serviceWorkerRegistration: serviceWorkerRegistration,
}).then((token) => {
console.log('my fcm token', token);
// This is a good place to then store it on your database for each user
});
});
this.message$ = new Observable<MessagePayload>((sub) =>
onMessage(this.msg, (msg) => sub.next(msg))).pipe(
tap((msg) => {
console.log("My Firebase Cloud Message", msg);
})
// Set listeners before calling `register` to avoid throwing.
this.listenForRegistrationChanges();
this.registerForMessages();
}

private listenForRegistrationChanges() {
onRegistered(this.msg, (installationId) => {
/* This is a good place to store it in your database for each user.
* This callback fires whenever `installationId` changes. */
console.log("my installation id", installationId);
});

onUnregistered(this.msg, (installationId) => {
// Drop it from your database. Sending messages to an unregistered ID results in a 404.
console.log("no longer registered", installationId);
});
}

private async registerForMessages() {
/* Request notification permission before calling `register`, otherwise
* `register` holds the app unstable until the user answers. */
if (
Notification.permission === "default" &&
await Notification.requestPermission() !== "granted"
) {
return;
}

// Register the service worker.
const serviceWorkerRegistration = await navigator.serviceWorker
.register("/assets/firebase-messaging-sw.js", { type: "module" });

/* Run `register` inside an injection context. Outside one AngularFire cannot wrap it, and
* warns. See `zones.md` for what wrapping adds. */
runInInjectionContext(this.injector, () =>
register(this.msg, {
vapidKey: `an optional public VAPID key you generate for your Firebase project`,
serviceWorkerRegistration,
}).catch((error) => console.error("could not register for messages", error))
);
}

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

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.

Here is a barebones Node example:
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.

```
```ts
export const sendTestMessage = onRequest(async (_, res) => {
try {
const message = {
Expand All @@ -152,7 +180,7 @@ export const sendTestMessage = onRequest(async (_, res) => {

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.

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

await Promise.all(promises);
} ```
}
```
2 changes: 1 addition & 1 deletion docs/version-21-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ng update @angular/fire # then AngularFire 21

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

- **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`.
- **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`.
- **Rewrites Vertex AI imports to AI Logic** (see below).

## Vertex AI is now Firebase AI Logic
Expand Down
Loading
Loading