基于Firebase+React的聊天应用:实现跨用户消息通知功能
实现Firebase+React聊天应用的跨用户消息推送通知
我正在用Firebase和React开发聊天应用,需要实现其他用户给当前用户发消息时,推送通知的功能。以下是我的消息发送模块(Input.js)和消息展示模块(Message.js)代码,求帮忙实现这个功能。
Input.js 消息发送模块
import React, { useContext, useState } from "react"; import Img from "../../static/chat/add-img.png"; import Attach from "../../static/chat/attach.png"; import { AuthContext } from "./AuthContext"; import { ChatContext } from "./ChatContext"; import { arrayUnion, doc, serverTimestamp, Timestamp, updateDoc, } from "firebase/firestore"; import { db, storage } from "../../firebase"; import { v4 as uuid } from "uuid"; import { getDownloadURL, ref, uploadBytesResumable } from "firebase/storage"; const Input = () => { const [text, setText] = useState(""); const [img, setImg] = useState(null); const { currentUser } = useContext(AuthContext); const { data } = useContext(ChatContext); const handleSend = async () => { if (img) { const storageRef = ref(storage, uuid()); const uploadTask = uploadBytesResumable(storageRef, img); uploadTask.on( (error) => { //TODO:Handle Error }, () => { getDownloadURL(uploadTask.snapshot.ref).then(async (downloadURL) => { await updateDoc(doc(db, "chats", data.chatId), { messages: arrayUnion({ id: uuid(), text, senderId: currentUser.uid, date: Timestamp.now(), img: downloadURL, }), }); }); } ); } else { await updateDoc(doc(db, "chats", data.chatId), { messages: arrayUnion({ id: uuid(), text, senderId: currentUser.uid, date: Timestamp.now(), }), }); } await updateDoc(doc(db, "userChats", currentUser.uid), { [data.chatId + ".lastMessage"]: { text, }, [data.chatId + ".date"]: serverTimestamp(), }); await updateDoc(doc(db, "userChats", data.user.uid), { [data.chatId + ".lastMessage"]: { text, }, [data.chatId + ".date"]: serverTimestamp(), }); setText(""); setImg(null); }; return ( <div className="input"> <input type="text" placeholder="Type something..." onChange={(e) => setText(e.target.value)} value={text} /> <div className="send"> <img src={Attach} alt="" /> <input type="file" style={{ display: "none" }} id="file" onChange={(e) => setImg(e.target.files[0])} /> <label htmlFor="file"> <img src={Img} alt="" /> </label> <button onClick={handleSend}>Send</button> </div> </div> ); }; export default Input;
Message.js 消息展示模块
import React, { useContext, useEffect, useRef } from "react"; import { AuthContext } from "./AuthContext"; import { ChatContext } from "./ChatContext"; const Message = ({ message }) => { const { currentUser } = useContext(AuthContext); const { data } = useContext(ChatContext); const ref = useRef(); useEffect(() => { ref.current?.scrollIntoView({ behavior: "smooth" }); }, [message]); return ( <div ref={ref} className={`message ${message.senderId === currentUser.uid && "owner"}`} > <div className="messageInfo"> <img src={ message.senderId === currentUser.uid ? currentUser.photoURL : data.user.photoURL } alt="" /> <span>just now</span> </div> <div className="messageContent"> <p>{message.text}</p> {message.img && <img src={message.img} alt="" />} </div> </div> ); }; export default Message;
实现方案
1. 配置Firebase Cloud Messaging(FCM)
在Firebase控制台启用FCM,将以下代码保存为firebase-messaging-sw.js,放到项目public目录:
importScripts('https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js'); importScripts('https://www.gstatic.com/firebasejs/9.22.0/firebase-messaging-compat.js'); firebase.initializeApp({ apiKey: "你的API_KEY", authDomain: "你的AUTH_DOMAIN", projectId: "你的PROJECT_ID", storageBucket: "你的STORAGE_BUCKET", messagingSenderId: "你的MESSAGING_SENDER_ID", appId: "你的APP_ID" }); const messaging = firebase.messaging(); messaging.onBackgroundMessage((payload) => { const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: '/firebase-logo.png' }; self.registration.showNotification(notificationTitle, notificationOptions); });
2. 集成FCM到React项目
在firebase.js中添加FCM相关配置:
import { getMessaging, getToken, onMessage } from "firebase/messaging"; import { doc, updateDoc } from "firebase/firestore"; import { db } from "./firebase"; // 初始化firebase后添加 export const messaging = getMessaging(); // 请求通知权限并保存设备令牌到用户文档 export const requestForToken = async (currentUser) => { try { const permission = await Notification.requestPermission(); if (permission === "granted") { const token = await getToken(messaging, { vapidKey: "你的VAPID_KEY" }); await updateDoc(doc(db, "users", currentUser.uid), { fcmToken: token }); } } catch (error) { console.error("获取FCM令牌失败:", error); } }; // 监听前台消息通知 export const onMessageListener = () => new Promise((resolve) => { onMessage(messaging, (payload) => { resolve(payload); }); });
3. 修改消息发送逻辑,触发通知
更新Input.js的handleSend函数,在发送消息后调用FCM发送通知:
// 在Input.js顶部添加导入 import { getDoc } from "firebase/firestore"; const handleSend = async () => { // ...原有消息发送代码 // 获取接收方的FCM令牌 const userDoc = await getDoc(doc(db, "users", data.user.uid)); const receiverToken = userDoc.data()?.fcmToken; if (receiverToken) { await fetch("https://fcm.googleapis.com/fcm/send", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "key=你的SERVER_KEY" // 从Firebase控制台获取 }, body: JSON.stringify({ to: receiverToken, notification: { title: currentUser.displayName, body: text || "发送了一张图片", click_action: `http://localhost:3000/chat/${data.chatId}` } }) }); } setText(""); setImg(null); };
4. 在聊天页面监听前台通知
在聊天主组件(如Chat.js)中添加通知监听:
import { useEffect, useContext } from "react"; import { AuthContext } from "./AuthContext"; import { requestForToken, onMessageListener } from "../firebase"; const Chat = () => { const { currentUser } = useContext(AuthContext); useEffect(() => { requestForToken(currentUser); const listenToMessages = async () => { const payload = await onMessageListener(); new Notification(payload.notification.title, { body: payload.notification.body, icon: currentUser.photoURL }); }; listenToMessages(); }, [currentUser]); // ...其他聊天组件代码 };
优化建议:使用Firebase云函数发送通知
避免在前端暴露SERVER_KEY,推荐用云函数触发通知:
// functions/index.js const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.sendNotification = functions.firestore.document("chats/{chatId}/messages/{messageId}") .onCreate(async (snap, context) => { const message = snap.data(); const chatId = context.params.chatId; // 获取聊天接收方ID const chatDoc = await admin.firestore().doc(`chats/${chatId}`).get(); const chatData = chatDoc.data(); const receiverId = chatData.userIds.find(id => id !== message.senderId); // 获取接收方FCM令牌 const receiverDoc = await admin.firestore().doc(`users/${receiverId}`).get(); const receiverToken = receiverDoc.data()?.fcmToken; if (receiverToken) { const payload = { notification: { title: message.senderName, // 需要在消息中存储发送者名称 body: message.text || "发送了一张图片", clickAction: `https://你的域名/chat/${chatId}` } }; await admin.messaging().sendToDevice(receiverToken, payload); } });
内容的提问来源于stack exchange,提问作者PHILLIPP APETENOK
相关产品推荐
相关产品推荐

