Node/Next.js中Pusher私有频道认证失败问题求助
我正在开发应用的消息通知系统,已编写ChatContext提供者文件包裹整个应用,用户登录后会自动绑定Pusher。每个用户拥有专属私有频道private-unread-${session.user.id},需确保仅当前登录用户可连接该频道。
调用Next.js的POST接口/pusher/auth时,持续出现以下错误:
SyntaxError: Unexpected token 's', "socket_id="... is not valid JSON
at JSON.parse ()
at parseJSONFromBytes (node:internal/deps/undici/undici:5329:19)
at successSteps (node:internal/deps/undici/undici:5300:27)
at fullyReadBody (node:internal/deps/undici/undici:1447:9)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async specConsumeBody (node:internal/deps/undici/undici:5309:7)
at async POST (webpack-internal:///(rsc)/./app/api/pusher/auth/route.js:20:34)
尝试改用jsonp+GET方式后出现其他随机问题,希望尽量保留ajax方式。
客户端上下文代码
'use client' import React, { createContext, useState, useEffect } from 'react'; import { useSession } from 'next-auth/react'; import PusherClient from 'pusher-js'; const ChatContext = createContext(); const ChatProvider = ({ children }) => { const { data: session } = useSession(); const [unreadChats, setUnreadChats] = useState([]); const [pusherConnected, setPusherConnected] = useState(false); useEffect(() => { if (session) { fetchUnreadMessages(); } }, [session]); useEffect(() => { if (session && !pusherConnected) { const pusher = new PusherClient(process.env.NEXT_PUBLIC_PUSHER_APP_KEY, { cluster: 'us2', encrypted: true, channelAuthorization: { endpoint: '/api/pusher/auth', params: { channel_name: `private-unread-${session.user.id}`, }, }, }); const channel = pusher.subscribe(`private-unread-${session.user.id}`); channel.bind('message:new', function (data) { console.log(data); setMessages(currentState => [data.message, ...currentState]) }); setPusherConnected(true); return () => { channel.unbind('message:new') }; } }, [session]) return ( <ChatContext.Provider value={{ unreadChats }}> {children} </ChatContext.Provider> ); }; export { ChatProvider, ChatContext };
认证路由代码
import PusherServer from 'pusher'; const pusherServer = new PusherServer({ appId: process.env.PUSHER_APP_ID, key: process.env.NEXT_PUBLIC_PUSHER_APP_KEY, secret: process.env.PUSHER_SECRET, cluster: 'us2', useTLS: true }); export const POST = async (req) => { try { const { socket_id, channel_name } = await req.json(); const token = **user's id pulled here from next auth** // Verify that the channel_name matches the user's ID (assuming channel_name is of the format `private-unread-<user_id>`) if (!channel_name.endsWith(token.sub)) { return new Response(JSON.stringify({ error: "Failed to auth pusher" }), { status: 403 }); } const auth = pusherServer.authorizeChannel(socket_id, channel_name) return new Response(JSON.stringify(auth), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.log(error); return new Response(JSON.stringify({ error: "Failed to auth pusher" }), { status: 403 }); } };
错误核心原因:Pusher默认以application/x-www-form-urlencoded格式发送认证请求,但后端接口在尝试解析JSON格式请求体,导致JSON解析失败。
1. 后端修改:解析表单数据而非JSON
将Next.js POST路由中的req.json()替换为req.formData(),正确获取Pusher发送的表单数据:
export const POST = async (req) => { try { const formData = await req.formData(); const socket_id = formData.get('socket_id'); const channel_name = formData.get('channel_name'); // 保留原有的用户ID获取逻辑 const token = **从next auth获取用户ID的代码** if (!channel_name.endsWith(token.sub)) { return new Response(JSON.stringify({ error: "Pusher认证失败" }), { status: 403 }); } const auth = pusherServer.authorizeChannel(socket_id, channel_name); return new Response(JSON.stringify(auth), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.log(error); return new Response(JSON.stringify({ error: "Pusher认证失败" }), { status: 403 }); } };
2. 客户端代码修复(可选但推荐)
- 修正未定义的
setMessages,改为对应状态setUnreadChats - 优化清理函数,取消频道订阅并重置连接状态,避免内存泄漏
useEffect(() => { if (session && !pusherConnected) { const pusher = new PusherClient(process.env.NEXT_PUBLIC_PUSHER_APP_KEY, { cluster: 'us2', encrypted: true, channelAuthorization: { endpoint: '/api/pusher/auth', params: { channel_name: `private-unread-${session.user.id}`, }, }, }); const channel = pusher.subscribe(`private-unread-${session.user.id}`); channel.bind('message:new', function (data) { console.log(data); setUnreadChats(currentState => [data.message, ...currentState]) }); setPusherConnected(true); return () => { channel.unbind('message:new'); pusher.unsubscribe(`private-unread-${session.user.id}`); setPusherConnected(false); }; } }, [session])
3. 可选:客户端显式指定请求格式
如果需要强制指定请求格式,可在channelAuthorization配置中添加头部信息:
channelAuthorization: { endpoint: '/api/pusher/auth', params: { channel_name: `private-unread-${session.user.id}`, }, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, transport: 'ajax', },
内容的提问来源于stack exchange,提问作者Sam Munroe

