React 中的 Firebase 推送通知
已發表: 2022-03-10如今,通知已成為網絡的穩定部分。 遇到請求向您的瀏覽器發送通知的許可的網站並不少見。 大多數現代 Web 瀏覽器都實現了推送 API,並且能夠處理推送通知。 對 caniuse 的快速檢查表明,該 API 在現代基於 chrome 的瀏覽器和 Firefox 瀏覽器中得到廣泛支持。
有多種服務可用於在 Web 上實現通知。 值得注意的是 Pusher 和 Firebase。 在本文中,我們將使用 Firebase 雲消息傳遞 (FCM) 服務實現推送通知,該服務是“一種跨平台消息傳遞解決方案,可讓您免費可靠地發送消息”。
我假設讀者對用 Express.js 編寫後端應用程序和/或對 React 有一定的了解。 如果您對這些技術中的任何一種都感到滿意,那麼您可以使用前端或後端。 我們將首先實現後端,然後再進行前端。 這樣,您可以使用對您更有吸引力的任何部分。
所以讓我們開始吧。
Firebase 消息的類型
Firebase 文檔指定 FCM 實現需要兩個組件。
- 受信任的環境,例如 Cloud Functions for Firebase 或在其上構建、定位和發送消息的應用服務器。
- 通過相應平台特定傳輸服務接收消息的 iOS、Android 或 Web (JavaScript) 客戶端應用程序。
我們將在我們的 express 後端應用程序中處理第 1 項,在我們的 react 前端應用程序中處理第 2 項。
文檔還指出,FCM 允許我們發送兩種類型的消息。
- 通知消息(有時被認為是“顯示消息”)由 FCM SDK 自動處理。
- 數據消息由客戶端應用程序處理。
通知消息由網絡上的瀏覽器自動處理。 它們還可以採用可選的data
負載,該負載必須由客戶端應用程序處理。 在本教程中,我們將發送和接收必須由客戶端應用程序處理的數據消息。 這為我們提供了更多自由來決定如何處理收到的消息。
設置 Firebase 項目
我們需要做的第一件事是建立一個 Firebase 項目。 FCM 是一項服務,因此我們需要一些 API 密鑰。 此步驟要求您擁有 Google 帳戶。 如果您還沒有,請創建一個。 您可以點擊這裡開始。
設置您的 Google 帳戶後,前往 Firebase 控制台。
點擊添加項目。 為您的項目輸入一個名稱,然後單擊continue 。 在下一個屏幕上,您可以選擇關閉分析。 您以後可以隨時從項目頁面的“分析”菜單中將其打開。 單擊繼續並等待幾分鐘以創建項目。 通常不到一分鐘。 然後單擊繼續打開您的項目頁面。
一旦我們成功設置了一個項目,下一步就是獲取必要的密鑰來處理我們的項目。 使用 Firebase 時,我們需要分別完成前端和後端的配置步驟。 讓我們看看如何獲得使用兩者所需的憑據。
前端
在項目頁面上,單擊圖標以將 Firebase 添加到您的網絡應用。

給你的應用一個暱稱。 無需設置 Firebase 託管。 單擊註冊應用程序並等待幾秒鐘以完成設置。 在下一個屏幕上,複製應用程序憑據並將其存儲在某處。 你可以讓這個窗口打開,稍後再回來。

稍後我們將需要配置對象。 單擊繼續控制台以返回到您的控制台。
後端
我們需要一個服務帳戶憑據來從後端連接我們的 Firebase 項目。 在您的項目頁面上,單擊項目概述旁邊的齒輪圖標以創建一個服務帳戶以用於我們的 Express 後端。 請參閱下面的屏幕截圖。 按照步驟 1 到 4 下載包含您的帳戶憑據的JSON
文件。 請務必將您的服務帳戶文件保存在安全的地方。

我建議您在準備好使用它之前不要下載它。 如果您需要復習,請記住回到這些部分。
所以現在我們已經成功地設置了一個 Firebase 項目並向它添加了一個網絡應用程序。 我們還了解瞭如何獲取我們在前端和後端工作所需的憑據。 現在讓我們從我們的快速後端發送推送通知。
入門
為了更容易完成本教程,我在 Github 上建立了一個項目,其中包含服務器和客戶端。 通常,您的後端和前端將分別擁有一個單獨的存儲庫。 但我將它們放在一起是為了更容易完成本教程。
創建一個 repo 的分支,將其克隆到您的計算機上,然後啟動我們的前端和後端服務器。
- fork 存儲庫並查看
01-get-started
分支。 - 在您選擇的代碼編輯器中打開項目並觀察其內容。
- 在項目根目錄中,我們有兩個文件夾,
client/
和server/
。 還有一個.editorconfig
文件、一個.gitignore
和一個README.md
。 - 客戶端文件夾包含一個 React 應用程序。 這是我們將監聽通知的地方。
- 服務器文件夾包含一個快速應用程序。 我們將從這裡發送通知。 該應用程序來自我們在我的另一篇文章 How To Set Up An Express API Backend Project With PostgreSQL 中構建的項目。
- 打開終端並導航到
client/
文件夾。 運行yarn install
命令安裝項目依賴。 然後運行yarn start
啟動項目。 訪問https://localhost:3000
以查看實時應用程序。 - 在
server/
文件夾中創建一個.env
文件並添加CONNECTION_STRING
環境變量。 此變量是指向 PostgreSQL 數據庫的數據庫連接 URL。 如果您需要這方面的幫助,請查看我的鏈接文章的Connecting The PostgreSQL Database And Writing A Model
部分。 您還應該提供PORT
環境變量,因為 React 已經在端口3000
上運行。 我在.env
文件中設置了PORT=3001
。 - 打開一個單獨的終端並導航到
server/
文件夾。 運行yarn install
命令安裝項目依賴。 運行yarn runQuery
創建項目數據庫。 運行yarn startdev
啟動項目。 訪問 https://localhost:3001/v1/messages,您應該會看到一些 JSON 格式的消息。



現在我們已經運行了前端和後端應用程序,讓我們在後端實現通知。
在後端設置 Firebase 管理員消息
在後端使用 FCM 發送推送通知需要 Firebase 管理 SDK 或 FCM 服務器協議。 我們將在本教程中使用管理 SDK。 還有通知編輯器,它非常適合“通過強大的內置定位和分析測試和發送營銷和參與信息”。
在您的終端中,導航到server/
文件夾並安裝 Admin SDK。
# install firebase admin SDK yarn add firebase-admin
打開您的.env
文件並添加以下環境變量。
GOOGLE_APPLICATION_CREDENTIALS="path-to-your-service-account-json-file"
此變量的值是您下載的服務帳戶憑據的路徑。 此時,您可能想回到我們為項目創建服務帳戶的部分。 您應該從那裡複製管理員初始化代碼並下載您的服務帳戶密鑰文件。 將此文件放在您的server/
文件夾中並將其添加到您的.gitignore
。
請記住,在實際項目中,您應該將此文件存儲在服務器上非常安全的位置。 不要讓它落入壞人之手。
打開server/src/settings.js
並導出應用程序憑據文件路徑。
# export the service account key file path export const googleApplicationCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
創建文件server/src/firebaseInit.js
並添加以下代碼。
import admin from 'firebase-admin'; import { googleApplicationCredentials } from './settings' const serviceAccount = require(googleApplicationCredentials); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'your-database-url-here' }); export const messaging = admin.messaging();
我們從firebase-admin
導入管理模塊。 然後,我們使用我們的服務帳戶文件初始化管理應用程序。 最後,我們創建並導出消息傳遞功能。
請注意,我可以直接將路徑傳遞給我的服務帳戶密鑰文件,但它是不太安全的選項。 處理敏感信息時始終使用環境變量。
要檢查您是否成功完成了初始化,請打開 server/src/app.js 並包含以下行。
import { messaging } from './firebaseInit' console.log(messaging)
我們導入消息傳遞實例並將其記錄在控制台中。 您應該看到類似於下圖的內容。 一旦您確認您的管理員設置正確,您應該刪除這些。

如果遇到任何問題,可以查看我的 repo 的 02-connect-firebase-admin 分支進行比較。
現在我們已經成功設置了管理員消息,現在讓我們編寫代碼來發送通知。
從後端發送推送通知
FCM 數據報文配置非常簡單。 您所要做的就是提供一個或多個目標以及您希望發送給客戶端的消息的JSON
。 JSON
中沒有必需的鍵。 您獨自決定要在數據中包含哪些鍵值對。 數據消息表單適用於所有平台,因此我們的通知也可以由移動設備處理。
其他平台還有其他配置。 例如,有一個僅適用於 android 設備的android
設置和僅適用於 iOS 設備的apns
設置。 您可以在此處找到配置指南。
創建文件server/src/notify.js
並輸入以下代碼。
import { messaging } from './firebaseInit'; export const sendNotificationToClient = (tokens, data) => { // Send a message to the devices corresponding to the provided // registration tokens. messaging .sendMulticast({ tokens, data }) .then(response => { // Response is an object of the form { responses: [] } const successes = response.responses.filter(r => r.success === true) .length; const failures = response.responses.filter(r => r.success === false) .length; console.log( 'Notifications sent:', `${successes} successful, ${failures} failed` ); }) .catch(error => { console.log('Error sending message:', error); }); };
我們創建了一個接受令牌字符串數組和數據對象的函數。 每個令牌字符串代表一個已接受從我們的後端應用程序接收通知的設備。 通知將發送到令牌數組中的每個客戶端。 我們將在教程的前端部分看到如何生成令牌。
消息傳遞實例的sendMulticast
方法返回一個承諾。 成功時,我們會得到一個數組,從中我們可以計算成功的次數以及失敗的通知。 您當然可以隨心所欲地處理此響應。
每次將新消息添加到數據庫時,讓我們使用此函數發送通知。
打開server/src/controllers/message.js
並更新addMessage
函數。
import { sendNotificationToClient } from '../notify'; export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); const tokens = []; const notificationData = { title: 'New message', body: message, }; sendNotificationToClient(tokens, notificationData); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
此函數處理對/messages
端點的發布請求。 成功創建消息後, sendNotificationToClient
函數將發出通知,然後將響應發送給客戶端。 此代碼中唯一缺少的部分是發送通知的tokens
。
當我們連接客戶端應用程序時,我們將復制生成的令牌並將其粘貼到此文件中。 在生產應用程序中,您將令牌存儲在數據庫中的某個位置。
使用最後一段代碼,我們已經完成了後端實現。 現在讓我們切換到前端。
此時我的倉庫中對應的分支是 03-send-notification。
在客戶端上設置 Firebase 消息通知
讓我們看一下我們前端 React 應用程序的主要組件。
打開client/src/App.js
並檢查內容。 我將省略大部分導入語句,只看程序邏輯。
# library imports import { Messaging } from './Messaging'; axios.defaults.baseURL = 'https://localhost:3001/v1'; const App = () => { return ( <Fragment> <ToastContainer autoClose={2000} position="top-center" /> <Navbar bg="primary" variant="dark"> <Navbar.Brand href="#home">Firebase notifictations with React and Express</Navbar.Brand> </Navbar> <Container className="center-column"> <Row> <Col> <Messaging /> </Col> </Row> </Container> </Fragment> ); }; export default App;
這是一個帶有 react-bootstrap 樣式的常規反應組件。 在我們的應用程序頂部有一個 toast 組件,我們將使用它來顯示通知。 請注意,我們還為axios
庫設置了baseURL
。 值得注意的一切都發生在<Messaging />
組件中。 現在讓我們來看看它的內容。
打開client/src/Messaging.js
並檢查內容。
export const Messaging = () => { const [messages, setMessages] = React.useState([]); const [requesting, setRequesting] = React.useState(false); React.useEffect(() => { setRequesting(true); axios.get("/messages").then((resp) => { setMessages(resp.data.messages); setRequesting(false); }); }, []); return ( <Container> {/* form goes here */} <div className="message-list"> <h3>Messages</h3> {requesting ? ( <Spinner animation="border" role="status"> <span className="sr-only">Loading...</span> </Spinner> ) : ( <> {messages.map((m, index) => { const { name, message } = m; return ( <div key={index}> {name}: {message} </div> ); })} </> )} </div> </Container> ); };
我們有兩個狀態變量, messages
和requesting
。 messages
表示來自我們數據庫的消息列表, requesting
用於切換我們的加載器狀態。 我們有一個React.useEffect
塊,我們在其中對/messages
端點進行 API 調用,並將返回的數據設置為我們的messages
狀態。

在 return 語句中,我們映射消息並顯示name
和message
字段。 在同一頁面上,我們包含一個用於創建新消息的表單。
<Formik initialValues={{ name: "", message: "", }} onSubmit={(values, actions) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); toast.success("Submitted succesfully"); }, 1000); }} > {(prop) => { const { handleSubmit, handleChange, isSubmitting } = prop; return ( <> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text>Name</InputGroup.Text> </InputGroup.Prepend> <FormControl placeholder="Enter your name" onChange={handleChange("name")} /> </InputGroup> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text>Message</InputGroup.Text> </InputGroup.Prepend> <FormControl onChange={handleChange("message")} placeholder="Enter a message" /> </InputGroup> {isSubmitting ? ( <Button variant="primary" disabled> <Spinner as="span" size="sm" role="status" animation="grow" aria-hidden="true" /> Loading... </Button> ) : ( <Button variant="primary" onClick={() => handleSubmit()}> Submit </Button> )} </> ); }} </Formik>
我們使用Formik
庫來管理我們的表單。 我們向<Formik />
組件傳遞了一個initialvalues
道具、一個onSubmit
道具和我們想要渲染的表單組件。 作為回報,我們得到了一些方便的函數,比如我們可以用來操作表單輸入的handleChange
,以及我們用來提交表單的handleSubmit
。 isSubmitting
是一個boolean
,我們用它來切換提交按鈕的狀態。
我鼓勵你嘗試一下formik。 它確實簡化了表單的處理。 稍後我們將替換onSubmit
方法中的代碼。
現在讓我們實現將請求瀏覽器權限並為其分配令牌的方法。
要開始在前端使用 Firebase,我們必須安裝 Firebase JavaScript 客戶端庫。 請注意,這是與firebase-admin SDK
不同的包。
# install firebase client library yarn add firebase
創建文件client/src/firebaseInit.js
並添加以下內容。
import firebase from 'firebase/app'; import 'firebase/messaging'; const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID" }; firebase.initializeApp(config); const messaging = firebase.messaging(); // next block of code goes here
Firebase 文檔指出:
“完整的 Firebase JavaScript 客戶端包括對 Firebase 身份驗證、Firebase 實時數據庫、Firebase 存儲和 Firebase 雲消息傳遞的支持。”
所以在這裡,我們只導入消息傳遞功能。 此時,您可以參考創建 Firebase 項目的部分來獲取config
對象。 然後我們初始化 Firebase 並導出消息傳遞功能。 讓我們添加最後的代碼塊。
export const requestFirebaseNotificationPermission = () => new Promise((resolve, reject) => { messaging .requestPermission() .then(() => messaging.getToken()) .then((firebaseToken) => { resolve(firebaseToken); }) .catch((err) => { reject(err); }); }); export const onMessageListener = () => new Promise((resolve) => { messaging.onMessage((payload) => { resolve(payload); }); });
requestFirebaseNotificationPermission
函數請求瀏覽器發送通知的權限,如果請求被授予,則使用令牌解析。 這是 FCM 用來向瀏覽器發送通知的令牌。 它會觸發您在瀏覽器上看到的請求發送通知權限的提示。
僅當瀏覽器處於前台時才會調用onMessageListener
函數。 稍後,我們將編寫一個單獨的函數來處理瀏覽器在後台時的通知。
打開client/src/App.js
並導入requestFirebaseNotificationPermission
函數。
import { requestFirebaseNotificationPermission } from './firebaseInit'
然後在 App 函數中,在 return 語句之前添加以下代碼。
requestFirebaseNotificationPermission() .then((firebaseToken) => { // eslint-disable-next-line no-console console.log(firebaseToken); }) .catch((err) => { return err; });
應用程序加載後,此功能將運行並請求瀏覽器顯示通知的權限。 如果授予權限,我們會記錄令牌。 在生產應用程序中,您應該將令牌保存在後端可以訪問的位置。 但是對於本教程,我們只是將令牌複製並粘貼到後端應用程序中。
現在運行您的應用程序,您應該會看到通知請求消息。 單擊允許並等待令牌記錄到控制台。 由於您已授予瀏覽器權限,如果我們刷新頁面,您將不會再看到橫幅,但令牌仍會記錄到控制台。

您應該知道 Firefox 瀏覽器 (v75) 默認情況下不會請求通知權限。 權限請求必須由用戶生成的操作(如點擊)觸發。
這是我提交更改的好點。 對應的分支是 04-request-permission。
現在讓我們完成將消息保存到數據庫的代碼。
打開client/src/Messaging.js
並將我們表單的onSubmit
函數替換為以下代碼。
onSubmit={(values, actions) => { axios .post("/messages", values) .then((resp) => { setMessages(resp.data.messages.concat(messages)); actions.setSubmitting(false); toast.success("Submitted succesfully"); }) .catch((err) => { console.log(err); toast.error("There was an error saving the message"); }); }}
我們向/messages
端點發出post
請求以創建新消息。 如果請求成功,我們將返回的數據放在messages
列表的頂部。 我們還展示了一個成功的祝酒詞。
讓我們試試看它是否有效。 啟動前端和後端服務器。 在嘗試發布請求之前,打開server/src/controllers/messages.js
並註釋掉我們發送通知的行。
# this line will throw an error if tokens is an empty array comment it out temporarily // sendNotificationToClient(tokens, notificationData);
嘗試向數據庫中添加一些消息。 作品? 那太棒了。 現在在繼續之前取消註釋該行。
從開發人員控制台複製通知令牌並將其粘貼到令牌數組中。 令牌是一個很長的字符串,如下所示。
const tokens = [ 'eEa1Yr4Hknqzjxu3P1G3Ox:APA91bF_DF5aSneGdvxXeyL6BIQy8wd1f600oKE100lzqYq2zROn50wuRe9nB-wWryyJeBmiPVutYogKDV2m36PoEbKK9MOpJPyI-UXqMdYiWLEae8MiuXB4mVz9bXD0IwP7bappnLqg', ];
打開client/src/Messaging.js
,導入onMessageListener
並在useEffect
塊下調用它。 函數中的任何位置都可以,只要它在return
語句之前。
import { onMessageListener } from './firebaseInit'; React.useEffect(() => { ... }, []); onMessageListener() .then((payload) => { const { title, body } = payload.data; toast.info(`${title}; ${body}`); }) .catch((err) => { toast.error(JSON.stringify(err)); });
偵聽器返回一個承諾,該承諾在成功時解析為通知有效負載。 然後我們在 toast 中顯示標題和正文。 請注意,一旦我們收到此通知,我們本可以採取任何其他行動,但我在這裡保持簡單。 在兩台服務器都運行的情況下,試一試,看看它是否正常工作。
作品? 那太棒了。
如果您遇到問題,您可以隨時與我的 repo 進行比較。 此時對應的分支是05-listen-to-notification。
我們只需要注意一點。 現在我們只能在瀏覽器處於前台時看到通知。 關於通知的要點是,無論瀏覽器是否在前台,它都應該彈出。
如果我們要發送顯示消息,即我們在通知負載中包含notification
對象,瀏覽器將自行處理。 但是由於我們正在發送數據消息,所以當我們的瀏覽器在後台時,我們必須告訴瀏覽器如何響應通知。
為了處理後台通知,我們需要向我們的前端客戶端註冊一個服務工作者。
創建文件client/public/firebase-messaging-sw.js
並輸入以下內容:
importScripts('https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js'); importScripts('https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js'); const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID" }; firebase.initializeApp(config); const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler(function(payload) { console.log('[firebase-messaging-sw.js] Received background message ', payload); const notificationTitle = payload.data.title; const notificationOptions = { body: payload.data.body, icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions); }); self.addEventListener('notificationclick', event => { console.log(event) return event; });
在文件的頂部,我們導入了firebase-app
和firebase-messaging
庫,因為我們只需要消息傳遞功能。 如果導入語法是新的,請不要擔心。 這是一種將外部腳本導入服務工作者文件的語法。 確保導入的版本與package.json
中的版本相同。 我遇到了通過協調版本解決的問題。
像往常一樣,我們初始化 Firebase,然後調用setBackgroundMessageHandler
,向它傳遞一個回調,該回調接收通知消息有效負載。 代碼的其餘部分指定瀏覽器應如何顯示通知。 請注意,我們還可以包含要顯示的圖標。
我們還可以使用notificationclick
事件處理程序控制單擊通知時發生的情況。
創建文件client/src/serviceWorker.js
並輸入以下內容。
export const registerServiceWorker = () => { if ('serviceWorker' in navigator) { navigator.serviceWorker .register('firebase-messaging-sw.js') .then(function (registration) { // eslint-disable-next-line no-console console.log('[SW]: SCOPE: ', registration.scope); return registration.scope; }) .catch(function (err) { return err; }); } };
這個函數註冊我們的服務工作者文件。 請注意,我們已經替換了 React 生成的更詳細的版本。 我們首先檢查serviceWorker
是否存在於navigator
器對像中。 這是簡單的瀏覽器支持。 如果瀏覽器支持服務工作者,我們註冊我們之前創建的服務工作者文件。
現在打開client/src/index.js
,導入這個函數,然後調用它。
# other imports import { registerServiceWorker } from './serviceWorker' ReactDOM.render( ... ); registerServiceWorker()
如果一切順利,您應該會看到服務工作人員的範圍已記錄到您的控制台。
在第二個瀏覽器中打開 https://localhost:3000/messaging 並創建一條消息。 您應該會看到來自其他瀏覽器的通知。

至此,我們已經結束了本教程。 我的倉庫中對應的分支是 06-handle-background-notification。
結論
在本文中,我們了解了可以使用 Firebase 雲消息傳遞 (FCM) 發送的不同類型的通知消息。 API。 然後我們在後端實現了“數據消息”類型。 最後,我們在客戶端應用程序上生成了一個令牌,用於接收後端應用程序觸發的通知消息。 最後,我們學習瞭如何在瀏覽器處於後台或前台時偵聽和顯示通知消息。
我鼓勵您查看 FCM 文檔以了解更多信息。
相關資源
- Firebase,官方網站
- Fireact,Orji Chidi 馬修,GitHub
- “Firebase:讓應用成功變得簡單”,npm 博客
- Firebase 控制台
- Firebase Admin Node.js SDK,npm 博客
- WebpushConfig、Firebase 文檔
sendMulticast
, Firebase 文檔- 服務工作者食譜,Mozilla
- 通知、Firebase 文檔
- Firebase 雲消息傳遞,Firebase 文檔
- “如何使用 PostgreSQL 設置 Express API 後端項目”,Chidi Orji