在 Next.js 中处理 Webhook 事件:触发 Vercel 中的前端更改

问题描述 投票:0回答:1

在我的 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;
next.js webhooks
1个回答
0
投票

首先,如果您的

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

© www.soinside.com 2019 - 2024. All rights reserved.