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

如何使用pusher-js订阅并接收服务器到用户频道的数据?

问题背景

我在Vercel上部署了Next.js应用,想通过Pusher实现实时消息功能,让每个用户通过私有安全频道接收消息。从日志看流程没问题,但就是没法成功订阅频道并接收事件消息,试了各种方法都没解决。


服务端代码

/pusher/user-auth/index.ts

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    const pusherService = container.resolve(PusherService);
    const manageToken = container.resolve(ManageToken);
    const loggingService = container.resolve(LoggingService);

    loggingService.info('PUSHER /api/pusher/user-auth - Authenticate user');

    const socketId = req.body.socket_id as string;
    const token = req.headers['x-csrf-token'] as string;

    const currentUser = await manageToken.verifyUserToken(token);

    const authResponse = await pusherService.authenticateUser(socketId, {
        id: currentUser.id,
        user_info: {
            lastName: currentUser.lastName,
            firstName: currentUser.firstName,
            email: currentUser.email,
        },
    });

    res.status(200).json(authResponse);
    return;
}

/pusher/auth/index.ts

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    const pusherService = container.resolve(PusherService);
    const loggingService = container.resolve(LoggingService);

    loggingService.info('PUSHER /api/pusher/auth - Authorize channel.');

    const socketId = req.body.socket_id as string;
    const channel = req.body.channel_name;
    const authResponse = pusherService.authorizeChannel(socketId, channel);
    res.status(200).json(authResponse);
    return;
}

/pusher-service.ts

public async sendToUser(userId: string, event: string, data: any) {
    this.loggingService.info('Send event to user via Pusher.');
    try {
        return this.pusher.sendToUser(userId, 'generated', {message: 'hi'});
    } catch (e) {
        this.loggingService.error(e);
    }
}

客户端代码

export const subscribe = async (token) => {
  log.info("Subscribe pusher channel.");

  Pusher.logToConsole = true;
  const pusher = new Pusher(process.env.REACT_APP_PUSHER_KEY, {
    cluster: "eu",
    channelAuthorization: {
      endpoint: process.env.REACT_APP_BLOOO_API_URL + "/pusher/auth"
    },
    userAuthentication: {
      endpoint: process.env.REACT_APP_BLOOO_API_URL + "/pusher/user-auth",
      headers: {
        "X-CSRF-Token": "Bearer " + token
      }
    }
  });

  pusher.signin();

  // 订阅示例(无法正常工作)
  // const channel = pusher.subscribe("#server-to-user-444") 
  // channel.bind("generated", (data) => {
  //   console.log(data);
  // });
};

客户端日志

Pusher :  : ["State changed","initialized -> connecting"]
logger.ts:19 Pusher :  : ["Connecting",{"transport":"ws","url":"wss://ws-eu.pusher.com:443/app/xxx?protocol=7&client=js&version=8.1.0&flash=false"}]
logger.ts:19 Pusher :  : ["State changed","connecting -> connected with new socket ID 123.123"]
logger.ts:19 Pusher :  : ["Event sent",{"event":"pusher:signin","data":{"auth":"123:123","user_data":"{\"id\":\"444\",\"user_info\":{\"lastName\":\"NAME\",\"firstName\":\"NAME\",\"email\":\"MAIL\"}}"}}]
logger.ts:19 Pusher :  : ["Event recd",{"event":"pusher:signin_success","data":{"user_data":"{\"id\":\"444\",\"user_info\":{\"lastName\":\"NAME\",\"firstName\":\"NAME\",\"email\":\"MAIL\"}}"}}]
logger.ts:19 Pusher :  : ["Event sent",{"event":"pusher:subscribe","data":{"auth":"","channel":"#server-to-user-444"}}]
logger.ts:19 Pusher :  : ["Event recd",{"event":"pusher_internal:subscription_succeeded","channel":"#server-to-user-444","data":{}}]
logger.ts:19 Pusher :  : ["No callbacks on #server-to-user-444 for pusher:subscription_succeeded"]
logger.ts:19 Pusher :  : ["Event recd",{"event":"generated","channel":"#server-to-user-444","data":{"message":"hi"}}]
logger.ts:19 Pusher :  : ["No callbacks on user for generated"]
logger.ts:19 Pusher :  : ["No callbacks on #server-to-user-444 for generated"]

问题排查与解决方案

从日志能看到,generated事件已经成功推送到#server-to-user-444频道,但客户端没触发回调,核心原因和修复方案如下:

1. 核心问题

  • 订阅代码未执行:客户端里的订阅和绑定代码是注释状态,根本没运行。
  • 实例作用域失效:pusher是函数内局部变量,函数执行完就被销毁,后续事件无法触发回调。
  • 订阅时机错误:pusher.signin()是异步操作,直接在后面写订阅代码可能还没完成认证就执行了。

2. 修正后的客户端代码

// 把pusher实例放在全局作用域或组件状态(比如React的useRef)中,避免被销毁
let pusherInstance = null;

export const subscribe = async (token) => {
  log.info("Subscribe pusher channel.");

  Pusher.logToConsole = true;
  pusherInstance = new Pusher(process.env.REACT_APP_PUSHER_KEY, {
    cluster: "eu",
    channelAuthorization: {
      endpoint: process.env.REACT_APP_BLOOO_API_URL + "/pusher/auth"
    },
    userAuthentication: {
      endpoint: process.env.REACT_APP_BLOOO_API_URL + "/pusher/user-auth",
      headers: {
        "X-CSRF-Token": "Bearer " + token
      }
    }
  });

  // 监听signin成功事件,确保认证完成后再订阅频道
  pusherInstance.bind('pusher:signin_success', (userData) => {
    const userId = userData.id;
    const channel = pusherInstance.subscribe(`#server-to-user-${userId}`);
    
    // 绑定事件回调
    channel.bind("generated", (data) => {
      console.log("收到实时消息:", data);
    });
  });

  pusherInstance.signin();
};

3. 额外检查点

  • 频道权限验证:服务端/pusher/auth接口要检查频道名称中的用户ID和当前认证用户ID是否匹配,防止无权限订阅。
  • 服务端发送逻辑:sendToUser方法里固定写死了事件名'generated',如果调用时传入的event参数没用上,要确认是否符合需求。
  • Token解析:客户端传递的X-CSRF-Token带Bearer 前缀,服务端verifyUserToken方法要先去掉前缀再验证。

内容的提问来源于stack exchange,提问作者laprof

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:37:08