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