在我的 Next.js 应用程序中,我面临着后端从 Webhook 接收事件的挑战,我需要在前端触发更改或执行操作以响应此事件。
流程如下:webhook将数据发送到应用程序的后端,我希望应用程序的前端做出相应的响应。
我正在使用 Vercel,似乎在这种情况下使用 WebSockets 可能不是一个可行的选择。我将不胜感激有关如何实现此功能的任何指导或建议。谢谢!
其他详细信息:
我尝试使用 next-ws 来处理 webhook,但即使在应用补丁后我也遇到了构建错误。作者指出
WebSocket 服务器(以及扩展的 Next WS)只能用于 服务器。 Vercel 是一个无服务器平台,这意味着 Next WS 无法 努力吧。
> Build error occurred
20:15:42.906 | Error: Next.js has not been patched to support Next WS, please run `npx next-ws-cli@latest patch`
20:15:42.906 | at Object.verifyPatch (/vercel/path0/node_modules/next-ws/server/utilities/patch.js:30:15)
20:15:42.906 | at Object.<anonymous> (/vercel/path0/next.config.js:1:27)
20:15:42.907 | at Module._compile (node:internal/modules/cjs/loader:1256:14)
20:15:42.907 | at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
20:15:42.907 | at Module.load (node:internal/modules/cjs/loader:1119:32)
20:15:42.907 | at Module._load (node:internal/modules/cjs/loader:960:12)
20:15:42.907 | at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:169:29)
20:15:42.907 | at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
20:15:42.923 | Error: Command "npm run build" exited with 1
20:15:43.082
在此之前,我也面临着 socket.io 和普通 WebSocket 实现的挑战,但不幸的是,我也无法让它们工作。
后端代码:
/api/webhook.ts
const express = require("express");
const bodyParser = require("body-parser");
const cryptoNode = require("crypto");
const app = express();
// Middleware to parse JSON and save raw body
app.use(
bodyParser.json({
verify: (req: any, res: any, buf: any) => {
req.rawBody = buf;
},
})
);
// Webhook route
app.post("/api/webhook", (req: any, res: any, next: any) => {
console.log("Received request with headers:", req.headers);
console.log("Received request with body:", req.rawBody);
const sigHashAlg = "sha256";
const sigHeaderName = "BTCPAY-SIG";
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!req.rawBody) {
return next("Request body empty");
}
const sig = Buffer.from(req.get(sigHeaderName) || "", "utf8");
const hmac = cryptoNode.createHmac(sigHashAlg, webhookSecret);
const digest = Buffer.from(
sigHashAlg + "=" + hmac.update(req.rawBody).digest("hex"),
"utf8"
);
const checksum = Buffer.from(sig.toString(), "utf8");
if (
checksum.length !== digest.length ||
!cryptoNode.timingSafeEqual(digest, checksum)
) {
console.log(`Request body digest did not match ${sigHeaderName}`);
return next(`Request body digest did not match ${sigHeaderName}`);
} else {
// Do More Stuff here
res.status(200).send("Request body was signed");
}
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
前端是这样的:
"use client";
import { useRouter } from "next/router";
import React, { useEffect } from "react";
function SomePage() {
const router = useRouter();
useEffect(() => {
// if something happend in the backend
router.push("/another-page");
}, []);
return <div className="">Something</div>;
}
export default SomePage;
首先,如果您的
backend and frontend
应用程序架构是分离的,那么我建议使用 socket.io
其次,假设您正在使用
socket.io
,好吗?
backend
应用程序的设置将是
const { createServer }= require("http");
const { Server } = require("socket.io");
const express = require("express");
const bodyParser = require("body-parser");
const cryptoNode = require("crypto");
const app = express();
// Middleware to parse JSON and save raw body
app.use(
bodyParser.json({
verify: (req: any, res: any, buf: any) => {
req.rawBody = buf;
},
})
);
// WebSocket implementation will start from here
const httpServer = createServer(app);
const io = new Server(httpServer, {
// options like "CORS" setup etc.
});
io.on("connection", (socket) => {
socket.on("ping", (data) => {
console.log(`Ping user connected successfully withthe data of: ${data}`);
socket.emit("pong", "hey, I'm pong")
});
});
// Webhook route
app.post("/api/webhook", (req: any, res: any, next: any) => {
console.log("Received request with headers:", req.headers);
console.log("Received request with body:", req.rawBody);
const sigHashAlg = "sha256";
const sigHeaderName = "BTCPAY-SIG";
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!req.rawBody) {
return next("Request body empty");
}
const sig = Buffer.from(req.get(sigHeaderName) || "", "utf8");
const hmac = cryptoNode.createHmac(sigHashAlg, webhookSecret);
const digest = Buffer.from(
sigHashAlg + "=" + hmac.update(req.rawBody).digest("hex"),
"utf8"
);
const checksum = Buffer.from(sig.toString(), "utf8");
if (
checksum.length !== digest.length ||
!cryptoNode.timingSafeEqual(digest, checksum)
) {
console.log(`Request body digest did not match ${sigHeaderName}`);
return next(`Request body digest did not match ${sigHeaderName}`);
} else {
// Do More Stuff here
io.sockets.emit("hi", "Your activities data, that you might be send to frontend")
res.status(200).send("Request body was signed");
}
});
// Start server
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
有关高级
socket.io
设置的更多详细信息:https://socket.io/docs/v4/server-api
第三,在您的
socket.io-client
应用程序中安装frontend
:npm install socket.io-client
并使用能够检测路线更改事件的HOC架构。
例如
Next.js
Layout
架构
在应用程序根目录中创建
layout
文件夹和index.jsx/index.tsx文件
"use client";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import webSocket, { Socket } from "socket.io-client";
//If you are using TS then use interface, otherwise skip
interface LayoutProps {
children?: JSX.Element[] | JSX.Element;
}
// const Layout = ({ children }: LayoutProps) => {
const Layout = ({ children }) => {
// Frontend WebSocket setup will start from here to detect the backed activities
const [socket, setSocket] = useState();
useEffect(() => {
const getSocket = webSocket("Your backend app URL");
socket.emit("ping", "Hey, I'm ping");
}, []);
// This useEffect will handle the backend "pong" namespace
useEffect(() => {
const getSocket = webSocket("Your backend app URL");
socket.on("pong", (data)=>{
console.log(`Pong server connected successfully withthe data of: ${data}`);
});
}, []);
// This useEffect will handle the backend activities "namespace"
useEffect(()=>{
socket?.on("hi", (data) => {
// if something happend in the backend
router.push("/another-page");
})
socket?.on("disconnect", () => {
socket.connect();
})
}, [socket]);
return <>{children}</>
}
export default Layout;
注意:
Frotend
应用程序 WebSocket 命名空间将与 Backend
应用程序命名空间
最后, 在根路由
_app.jsx/_app.tsx
文件夹中创建一个文件 pages
,并使用布局组件和 _app.jsx/_app.tsx 文件来包装您的主应用程序组件
import Layout from "../layout";
const YourApp = ({ Component, pageProps }) => {
// Others setup or some other advances implimentation
return(
<Layout>
<ComponentToRender {...pageProps} />
</Layout>
);
}
export default YourApp;
*** If you don't understand after reading this mass implementation, then send me a message, and we can have a meeting to set up your application architecture