You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:55:22