使用 React、Socket.io 和 Push Notifications 构建实时事件警报系统 🚀

2025-05-25

使用 React、Socket.io 和 Push Notifications 构建实时事件警报系统 🚀

这篇文章是关于什么的?

您希望一旦发生某件事,每个人都能在您的网站上收到消息。

这可以是新版本、公告或一些产品更新。

在本文中,我将指导您使用 React 和 Socket.io 构建一个事件调度系统。该应用程序允许用户为即将发生的事件设置提醒,并在事件发生时显示一个提示框。

Toaster = 屏幕上带有通知的小框。

吐司

有多种方法可以从服务器获取有关新事件的实时信息:

  1. 使用长轮询 HTTP 请求,基本上每 10 - 30 秒发送一次 HTTP 请求来获取有关新事件的信息。

  2. 当新的出价到达时,使用开放套接字(Websockets)直接从服务器获取信息。

在本文中,我将讨论 Websockets,特别是 Node.js 库 - Socket.io

Socket.io 是一个流行的 JavaScript 库,它允许我们在 Web 浏览器和 Node.js 服务器之间创建实时的双向通信。它是一个高性能且可靠的库,经过优化,能够以最小的延迟处理大量数据。它遵循 WebSocket 协议,并提供更强大的功能,例如回退到 HTTP 长轮询或自动重新连接,这使我们能够构建高效的实时应用程序。

Novu——第一个开源通知基础设施

简单介绍一下我们。Novu 是第一个开源通知基础设施。我们主要负责管理所有产品通知。通知可以是应用内通知(类似于开发者社区的Websockets中的铃铛图标)、电子邮件、短信等等。

如果你能给我们一颗星,我会非常高兴!这会帮助我每周写更多文章🚀
https://github.com/novuhq/novu

诺武

将 Socket.Io 与 React 连接起来😎

在这里,我们将为事件调度系统搭建项目环境。您还将学习如何将 Socket.io 添加到 React 和 Node.js 应用程序中,以及如何通过 Socket.io 连接两个开发服务器进行实时通信。

创建包含两个名为客户端和服务器的子文件夹的项目文件夹。

mkdir event-scheduler
cd event-scheduler
mkdir client server
Enter fullscreen mode Exit fullscreen mode

通过终端导航到客户端文件夹并创建一个新的 React.js 项目。

cd client
npx create-react-app ./
Enter fullscreen mode Exit fullscreen mode

安装 Socket.io 客户端 API 和 React Router。React  Router 是一个 JavaScript 库,它使我们能够在 React 应用程序中的页面之间导航。

npm install socket.io-client react-router-dom
Enter fullscreen mode Exit fullscreen mode

从 React 应用中删除多余的文件(例如徽标和测试文件),并更新App.js文件以显示如下所示的 Hello World。

function App() {
    return (
        <div>
            <p>Hello World!</p>
        </div>
    );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

导航到服务器文件夹并创建一个package.json文件。

cd server & npm init -y
Enter fullscreen mode Exit fullscreen mode

安装 Express.js、CORS、Nodemon 和 Socket.io 服务器 API。

Express.js 是一个快速、简约的框架,它提供了多种用于在 Node.js 中构建 Web 应用程序的功能。CORS 是一个 Node.js ,允许不同域之间进行通信。

Nodemon 是一个 Node.js 工具,它在检测到文件更改后会自动重启服务器,而 Socket.io 允许我们在服务器上配置实时连接。

npm install express cors nodemon socket.io
Enter fullscreen mode Exit fullscreen mode

创建一个index.js文件——Web 服务器的入口点。

touch index.js
Enter fullscreen mode Exit fullscreen mode

使用 Express.js 设置一个简单的 Node.js 服务器。下面的代码片段会在浏览器中访问时返回一个 JSON 对象http://localhost:4000/api

//index.js
const express = require("express");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

导入 HTTP 和 CORS 库以允许客户端和服务器域之间进行数据传输。

const express = require("express");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

//New imports
const http = require("http").Server(app);
const cors = require("cors");

app.use(cors());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

http.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

接下来,将 Socket.io 添加到项目中,以创建实时连接。在app.get()代码块之前,复制以下代码。接下来,将 Socket.io 添加到项目中,以创建实时连接。在app.get()代码块之前,复制以下代码。

//New imports
.....
const socketIO = require('socket.io')(http, {
    cors: {
        origin: "http://localhost:3000"
    }
});

//Add this before the app.get() block
socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);
    socket.on('disconnect', () => {
      console.log('🔥: A user disconnected');
    });
});
Enter fullscreen mode Exit fullscreen mode

从上面的代码片段中,该socket.io("connection")函数与 React 应用程序建立连接,然后为每个套接字创建一个唯一的 ID,并在用户访问网页时将该 ID 记录到控制台。

当您刷新或关闭网页时,套接字会触发断开事件,表明用户已与套接字断开连接。

通过将启动命令添加到package.json文件中的脚本列表中来配置 Nodemon。下面的代码片段使用 Nodemon 启动服务器。

//In server/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
Enter fullscreen mode Exit fullscreen mode

您现在可以使用以下命令通过 Nodemon 运行服务器。

npm start
Enter fullscreen mode Exit fullscreen mode

打开App.js客户端文件夹中的文件并将 React 应用程序连接到 Socket.io 服务器。

import socketIO from "socket.io-client";
const socket = socketIO.connect("http://localhost:4000");

function App() {
    return (
        <div>
            <p>Hello World!</p>
        </div>
    );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

启动 React.js 服务器。

npm start
Enter fullscreen mode Exit fullscreen mode

检查服务器运行的终端;React.js 客户端的 ID 应该出现在终端上。

恭喜🥂,React 应用程序已成功通过 Socket.io 连接到服务器。

应用程序界面📲

在本节中,我将指导您构建事件调度系统的用户界面。它是一个单页应用程序,包含三个组件:数字时钟、用于创建事件调度的表单字段以及列出即将发生的事件的部分。

日程

更新App.js文件如下:

import React from "react";
import Clock from "./components/Clock";
import CreateSchedule from "./components/CreateSchedule";
import Schedules from "./components/Schedules";
import socketIO from "socket.io-client";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    return (
        <div className='app__container'>
            <Clock />
            <CreateSchedule />
            <Schedules />
        </div>
    );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

导航到src/index.css文件并复制以下代码。它包含设计此项目所需的所有 CSS。

@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
* {
    box-sizing: border-box;
    font-family: "Space Grotesk", sans-serif;
}
body {
    margin: 0;
    padding: 0;
}
.app__container {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-height: 100vh;
    padding: 30px;
}
.clock__container {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 20vh;
    background-color: #f0ebe3;
    margin-bottom: 30px;
}
.clock {
    font-size: 50px;
    color: #576f72;
}
.createSchedule__container {
    margin-bottom: 30px;
    width: 70%;
}
form div {
    margin-bottom: 20px;
}
input {
    margin-left: 20px;
    padding: 5px;
    width: 100px;
}
#title {
    width: 70%;
}
button {
    width: 200px;
    padding: 15px;
    font-size: 16px;
    border: none;
    outline: none;
    background-color: #576f72;
    color: #fff;
    cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

时钟组件⏰

将代码复制到Clock.js文件中以显示当前时间。

import React, { useState, useEffect } from "react";

const Clock = () => {
    const [date, setDate] = useState(new Date());

    const refreshClock = () => setDate(new Date());

    useEffect(() => {
        const timerId = setInterval(refreshClock, 1000);
        return () => clearInterval(timerId);
    }, []);

    return (
        <div className='clock__container'>
            <h2 className='clock'>{date.toLocaleTimeString()}</h2>
        </div>
    );
};

export default Clock;
Enter fullscreen mode Exit fullscreen mode

构建 Schedule 组件📅

将以下代码复制到CreateSchedule.js文件中。它会显示一个表单,允许用户设置事件的时间和标题。

import React, { useState } from "react";

const CreateSchedule = () => {
    const [hour, setHour] = useState(0);
    const [minute, setMinute] = useState(0);
    const [title, setTitle] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log({ hour, minute, title });
        setHour("");
        setMinute("");
        setTitle("");
    };
    return (
        <div className='createSchedule__container'>
            <h2>Create a Schedule</h2>
            <form onSubmit={handleSubmit}>
                <div className='title__container'>
                    <label htmlFor='title'>Title</label>
                    <input
                        type='text'
                        name='title'
                        id='title'
                        value={title}
                        required
                        onChange={(e) => setTitle(e.target.value)}
                    />
                </div>

                <div>
                    <label htmlFor='hour'>Select Hour</label>
                    <input
                        type='number'
                        min={0}
                        max={23}
                        name='hour'
                        id='hour'
                        value={hour}
                        onChange={(e) => setHour(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label htmlFor='minute'>Select Minute</label>
                    <input
                        type='number'
                        min={0}
                        max={59}
                        name='minute'
                        id='minute'
                        value={minute}
                        onChange={(e) => setMinute(e.target.value)}
                        required
                    />
                </div>
                <button>Submit</button>
            </form>
        </div>
    );
};

export default CreateSchedule;
Enter fullscreen mode Exit fullscreen mode

多个计划组件🗓📅

下面的代码片段显示了即将发生的事件。

import React from "react";

const Schedules = () => {

    //create dummy schedules for now
    const schedules = [
        {title: "Build React project", hour: 12, minute: 0},
        {title: "Dance class", hour: 14, minute: 30}]
    return (
        <div>
            <h2>Upcoming Events</h2>
            <ul>
                {schedules?.map((schedule) => (
                    <li key={schedule.title}>
                        {schedule.title} - {schedule.hour} : {schedule.minute}
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default Schedules;
Enter fullscreen mode Exit fullscreen mode

将应用程序连接到Socket.io服务器

在本节中,您将学习如何将事件计划保存到 Node.js 服务器,并在特定事件发生时向 React 应用程序发送通知。

将事件保存到后端服务器💾

在这里,我将指导您将计划发送并保存到后端 Node.js 服务器。

将 Socket.io 传递到文件CreateSchedule中的组件中App.js

import React from "react";
import Clock from "./components/Clock";
import CreateSchedule from "./components/CreateSchedule";
import Schedules from "./components/Schedules";
import socketIO from "socket.io-client";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    return (
        <div className='app__container'>
            <Clock />
            <CreateSchedule socket={socket} />
            <Schedules />
        </div>
    );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

更新handleSubmit函数以通过 Socket.io 将事件详细信息发送到 Node.js 服务器。

import React, { useState } from "react";

const CreateSchedule = ({ socket }) => {
    const [hour, setHour] = useState(0);
    const [minute, setMinute] = useState(0);
    const [title, setTitle] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        //sends the event details via Socket.io
        socket.emit("newEvent", { hour, minute, title });
        setHour("");
        setMinute("");
        setTitle("");
    };
    return <div className='createSchedule__container'>...</div>;
};

export default CreateSchedule;

Enter fullscreen mode Exit fullscreen mode

在 Node.js 服务器上创建事件监听器,用于接收来自 React 应用的数据。以下代码片段将事件详情记录到终端。

socketIO.on("connection", (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);

    //event listener for new events
    socket.on("newEvent", (event) => {
        console.log(event);
    });

    socket.on("disconnect", () => {
        socket.disconnect();
    });
});
Enter fullscreen mode Exit fullscreen mode

将事件保存到数组并将事件列表发送回 React 应用程序。

let eventList = [];

socketIO.on("connection", (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);

    /*
    The event listener adds the new event 
        to the top of the array, and 
        sends the array to the React app
    */
    socket.on("newEvent", (event) => {
        eventList.unshift(event);
        //sends the events back to the React app
        socket.emit("sendSchedules", eventList);
    });

    socket.on("disconnect", () => {
        socket.disconnect();
    });
});
Enter fullscreen mode Exit fullscreen mode

sendSchedules为文件中的消息创建事件监听器App.js

import React, { useEffect, useState } from "react";
import Clock from "./components/Clock";
import CreateSchedule from "./components/CreateSchedule";
import Schedules from "./components/Schedules";
import socketIO from "socket.io-client";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    const [schedules, setSchedules] = useState([]);

    useEffect(() => {
        //listens for the event list from the backend
        socket.on("sendSchedules", (schedules) => {
            setSchedules(schedules);
        });
    }, []);

    return (
        <div className='app__container'>
            <Clock />
            <CreateSchedule socket={socket} />
            <Schedules schedules={schedules} />
        </div>
    );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

上面的代码片段接受来自后端的事件列表,并将事件(时间表)数组传递到Schedules组件中。

更新Schedules组件如下:

import React from "react";

const Schedules = ({ schedules }) => {
    return (
        <div>
            <h2>Upcoming Events</h2>
            <ul>
                {schedules?.map((schedule) => (
                    <li key={schedule.title}>
                        {schedule.title} - {schedule.hour} : {schedule.minute}
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default Schedules;

Enter fullscreen mode Exit fullscreen mode

恭喜!💃🏻 我们已经能够在服务器上保存事件并在客户端显示它们了。接下来,让我们在事件发生时向用户发送通知。

使用 React Toastify 添加通知

在本节中,我将指导您在用户添加事件时以及事件发生时向 React 应用程序发送通知。

安装并配置 React Toastify如下:

//In App.js file
import React, { useEffect, useState } from "react";
import Clock from "./components/Clock";
import CreateSchedule from "./components/CreateSchedule";
import Schedules from "./components/Schedules";
import socketIO from "socket.io-client";

//React Toastify imports
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer, toast } from "react-toastify";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    const [schedules, setSchedules] = useState([]);

    useEffect(() => {
        socket.on("sendSchedules", (schedules) => {
            setSchedules(schedules);
        });
    }, []);

    return (
        <div className='app__container'>
            <Clock />
            <CreateSchedule socket={socket} />
            <Schedules schedules={schedules} />
            <ToastContainer />
        </div>
    );
};

export default App
Enter fullscreen mode Exit fullscreen mode

更新CreateSchedule组件以在用户添加事件计划时显示通知。

import React, { useState } from "react";
import { toast } from "react-toastify";

const CreateSchedule = ({ socket }) => {
    const [hour, setHour] = useState(0);
    const [minute, setMinute] = useState(0);
    const [title, setTitle] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        socket.emit("newSchedule", { hour, minute, title });
        //👇🏻 shows toast notifications
        toast.success(`${title} is successfully added!`);
        setHour("");
        setMinute("");
        setTitle("");
    };
    return <div className='createSchedule__container'>...</div>;
};

export default CreateSchedule;
Enter fullscreen mode Exit fullscreen mode

更新server/index.js

let eventList = [];

socketIO.on("connection", (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);

    socket.on("newEvent", (event) => {
        eventList.unshift(event);
        socket.emit("sendSchedules", eventList);
    });
    /*
    The code snippet loops through the event list 
    and checks if it's time for any event 
    before sending a message containing 
    the event details to the React app
    */

    let interval = setInterval(function () {
        if (eventList.length > 0) {
            for (let i = 0; i < eventList.length; i++) {
                if (
                    Number(eventList[i].hour) === new Date().getHours() &&
                    Number(eventList[i].minute) === new Date().getMinutes() &&
                    new Date().getSeconds() === 0
                ) {
                    socket.emit("notification", {
                        title: eventList[i].title,
                        hour: eventList[i].hour,
                        mins: eventList[i].minute,
                    });
                }
            }
        }
    }, 1000);

    socket.on("disconnect", () => {
        socket.disconnect();
    });
});
Enter fullscreen mode Exit fullscreen mode

更新App.js文件以在事件发生时显示通知。

import React, { useEffect, useState } from "react";
import Clock from "./components/Clock";
import CreateSchedule from "./components/CreateSchedule";
import Schedules from "./components/Schedules";
import socketIO from "socket.io-client";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    const [schedules, setSchedules] = useState([]);

    useEffect(() => {
        socket.on("sendSchedules", (schedules) => {
            setSchedules(schedules);
        });
        //Listens for the notification from the server
        socket.on("notification", (data) => {
            toast.success(` It's time for ${data.title}`);
        });
    }, []);

    return (
        <div className='app__container'>
            <Clock />
            <CreateSchedule socket={socket} />
            <Schedules schedules={schedules} />
            <ToastContainer />
        </div>
    );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

您已完成基本事件安排,并在浏览器上显示它💁🏻‍♀️

额外:向用户发送推送通知🙋🏻‍♂️

如果网页打不开怎么办?如何确保用户不会错过行程,并且即使不在网页上也能收到通知?
推送通知是最好的解决方案。

即使 Web 应用程序未打开,推送通知也会出现在您的桌​​面或主屏幕上。在这里,我将指导您如何使用 Firebase 和 Novu 发送推送通知。

推送通知

设置 Firebase 云消息传递

转到 Firebase 控制台,登录并创建一个 Firebase 项目。

点击图标,在项目中创建一个 Firebase Web 应用</>
创建一个src/firebase.js文件,并将 Firebase JavaScript SDK 配置复制到该文件中

//👉🏻 In client/src/firebase.js
import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
Enter fullscreen mode Exit fullscreen mode

将 Firebase 安装到您的 React 项目。

cd client
npm install firebase
Enter fullscreen mode Exit fullscreen mode

点击 Firebase 应用信息中心侧边栏上“项目概览”旁边的“设置”图标,然后选择“项目设置”。
导航到“云消息传递”选项卡并生成 Web Push 证书密钥对。

Firebase

更新firebase.js文件以包含以下代码并添加 Web Push 证书作为vapidKey变量。

import { initializeApp } from "firebase/app";
import { getMessaging, getToken, onMessage } from "firebase/messaging";

const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "...",
};

// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
//Access Firebase cloud messaging
const messaging = getMessaging(firebaseApp);

/*
This function allows us to get your device token from Firebase 
which is required for sending Push notifications to your device.
*/
export const getTokenFromFirebase = () => {
    return getToken(messaging, {
        vapidKey: "<YOUR_WEB_PUSH_CERTIFICATE>",
    })
        .then((currentToken) => {
            if (currentToken) {
                console.log("current token for client: ", currentToken);
            } else {
                console.log(
                    "No registration token available. Request permission to generate one."
                );
            }
        })
        .catch((err) => {
            console.log("An error occurred while retrieving token. ", err);
        });
};

//This function listens to push messages on the server
export const onMessageListener = () =>
    new Promise((resolve) => {
        onMessage(messaging, (payload) => {
            console.log(payload);
            resolve(payload);
        });
    });
Enter fullscreen mode Exit fullscreen mode

在 React 应用程序的公共文件夹中创建一个服务工作文件。

cd client/public
touch firebase-messaging-sw.js
Enter fullscreen mode Exit fullscreen mode

将下面的代码片段复制到firebase-messaging-sw.js文件中。

// Scripts for firebase and firebase messaging
importScripts(
    "https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js"
);
importScripts(
    "https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js"
);

const firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "...",
};

firebase.initializeApp(firebaseConfig);

// Retrieve firebase messaging
const messaging = firebase.messaging();

messaging.onBackgroundMessage(function (payload) {
    console.log("Received background message ", payload);
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
    };

    self.registration.showNotification(notificationTitle, notificationOptions);
});
Enter fullscreen mode Exit fullscreen mode

导航到App.js文件并调用文件中的函数firebase.js

//....other imports

//👉🏻 Import the functions from the Firebase.js file
import { getTokenFromFirebase, onMessageListener } from "./firebase";

const socket = socketIO.connect("http://localhost:4000");

const App = () => {
    const [schedules, setSchedules] = useState([]);

    useEffect(() => {
        //👉🏻Logs the device token to the console
        getTokenFromFirebase();

    //👉🏻Listen and logs the push messages from the server.
        onMessageListener()
            .then((payload) => {
                console.log("From Message", payload);
            })
            .catch((err) => console.log("failed: ", err));

       //....socket.io listeners
    }, []);

    return (
        ...
    );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

运行 React 应用服务器;你的设备令牌将会显示在控制台中。复制并保存该令牌,以便在后续部分使用。

导出 Firebase

返回 Firebase 应用信息中心。在“项目设置”下,导航到“服务帐户”选项卡,然后点击Generate a new private key按钮下载您的服务帐户凭据(JSON 文件类型)。

Firebase

Novu——开源库

一旦有新的通知,我们可以使用 Novu 向用户发送推送通知,您也可以使用其他服务(例如OneSignal)或者您可以自己实现。

导航到客户端文件夹并通过运行以下代码创建一个 Novu 项目。

cd client
npx novu init
Enter fullscreen mode Exit fullscreen mode

在创建 Novu 项目之前,您需要使用 Github 登录。以下代码片段包含运行后应遵循的步骤 npx novu init

Now let's setup your account and send your first notification
❓ What is your application name? Devto Clone
❓ Now lets setup your environment. How would you like to proceed?
   > Create a free cloud account (Recommended)
❓ Create your account with:
   > Sign-in with GitHub
❓ I accept the Terms and Condidtions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy)
    > Yes
✔️ Create your account successfully.

We've created a demo web page for you to see novu notifications in action.
Visit: http://localhost:57807/demo to continue
Enter fullscreen mode Exit fullscreen mode

访问演示网页 http://localhost:57807/demo,复制您的订阅者 ID,然后点击“跳过教程”按钮。我们将在本教程的后续部分使用它。

演示

在仪表板上的集成商店部分下选择推送,将已下载的服务帐户凭据的内容复制到输入字段中并保存。

流式细胞术

从侧边栏中选择通知,并为推送消息创建通知模板。

通过将 Push 添加到其工作流程来编辑模板

推

选择推送步骤,添加推送通知内容,保存。

接下来,在服务器上安装并配置 Novu。

/*
👉🏻 Run npm install @novu/node in your terminal
*/
const { Novu, PushProviderIdEnum } = require("@novu/node");
const novu = new Novu("<YOUR_API_KEY>");

Enter fullscreen mode Exit fullscreen mode

API 密钥

通过文件中的 Novu 发送推送通知请求server/index.js,更新/api路由如下:

app.get("/api", async (req, res) => {
    const subscriberId = "<YOUR_NOVU_SUBSCRIBER_ID>";
    await novu.subscribers.identify(subscriberId, {
        firstName: "<YOUR_FIRST_NAME>",
        lastName: "<YOUR_LAST_NAME>",
    });

    await novu.subscribers.setCredentials(subscriberId, PushProviderIdEnum.FCM, {
        deviceTokens: ["<YOUR_DEVICE_TOKEN_FROM_REACT_APP_CONSOLE>"],
    });

    const trigger = await novu.trigger("<NOTIFICATION_TEMPLATE_ID>", {
        to: {
            subscriberId,
        },
    });

    res.json(trigger.data);
});
Enter fullscreen mode Exit fullscreen mode

恭喜!我们已经可以通过 Novu 发送推送通知了。

注意:当您检查 React 应用程序 JavaScript 控制台时,它还会记录推送通知内容。

设计

要在事件发生时发送通知,请创建一个函数,在事件发生时通过 Novu 发送推送通知,如下所示:

async function sendNotification(message) {
    const subscriberId = "<YOUR_NOVU_SUBSCRIBER_ID>";
    await novu.subscribers.identify(subscriberId, {
        firstName: "<YOUR_FIRST_NAME>",
        lastName: "<YOUR_LAST_NAME>",
    });

    await novu.subscribers.setCredentials(subscriberId, PushProviderIdEnum.FCM, {
        deviceTokens: ["<YOUR_DEVICE_TOKEN_FROM_REACT_APP_CONSOLE>"],
    });

    const trigger = await novu.trigger("<NOTIFICATION_TEMPLATE_ID>", {
        to: {
            subscriberId,
        },
        /*👇🏻 payload allows you to pass data into the notification template
        Read more here: https://docs.novu.co/platform/templates/#variable-usage
        */
        payload: {
            message,
        },
    });
}

socketIO.on("connection", (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);

    socket.on("newSchedule", (schedule) => {
        eventList.unshift(schedule);
        socket.emit("sendSchedules", eventList);
    });

    let interval = setInterval(function () {
        if (eventList.length > 0) {
            for (let i = 0; i < eventList.length; i++) {
                if (
                    Number(eventList[i].hour) === new Date().getHours() &&
                    Number(eventList[i].minute) === new Date().getMinutes() &&
                    new Date().getSeconds() === 0
                ) {
                    sendNotification(eventList[i].title);
                }
            }
        }
    }, 1000);

    socket.on("disconnect", () => {
        socket.disconnect();
    });
});
Enter fullscreen mode Exit fullscreen mode

结论

到目前为止,您已经学习了如何向 React 应用程序添加数字时钟、在 React 应用程序和 Socket.io 服务器之间间隔发送消息和通知以及如何使用 Novu 和 Firebase 向用户发送推送通知。

您还可以使用 Novu 和 Firebase 改进此应用程序并向各种应用程序添加推送通知。

本教程的源代码可以在这里找到:https://github.com/novuhq/blog/tree/main/event-scheduler-with-push-notifications

感谢您的阅读!

爱你

文章来源:https://dev.to/novu/building-a-live-event-alert-system-with-react-socketio-and-push-notifications-2jg5
PREV
使用 Socket.io 和 React 构建类似 Notion 的系统
NEXT
使用 React、NodeJS 和 ChatGPT 构建 JSON 到 Typescript 的转换器 🚀 TL;DR