跳到主要内容

后台监听与通知桥接

本文档说明第三方后台如何接收 dispatch-web 的会议、呼叫、软手柄通知,并通过 @scooper/meeting-sdk 转发给第三方页面。

什么时候需要后台桥接

默认接入方式是前端直接加载 scooper.dispatch.js,由它连接 dispatch-web 的 CometD/SSE 并触发 SDK callback。以下场景建议使用后台桥接:

  • 第三方页面不能直连 dispatch-web,存在跨域、网络隔离或统一网关限制。
  • 第三方希望后台统一做鉴权、日志、消息过滤、脱敏或多端转发。
  • 第三方移动端、桌面端、H5 需要共用同一套通知出口。

桥接后,前端收到的仍是 SDK 统一事件,例如 meeting:statusmeeting:membercall:statusdispatch:disconnect

监听链路

dispatch-web CometD channel
-> 第三方后台 CometD client
-> createScooperBackendListener(...)
-> createSseHandler(...) / attachWebSocketServer(...)
-> SDK backendListener
-> sdk.on(...) / callbacks.onX(...)

如果前端同时启用了 scooper.dispatch.js 直连和 backendListener 桥接,部分事件会收到两次。生产环境通常二选一:内网直连用默认方式,跨域/网关场景用后台桥接。

后台订阅计划

SDK 服务端模块复用前端 SDK 的 DISPATCH_EVENT_MAP,默认会订阅以下类型的 channel:

类型示例 channelSDK 事件
连接/conn, /cdispatch/disconnectdispatch:connectionChanged, dispatch:disconnect
呼叫/{accId}/call/in, /{accId}/call/status, /{accId}/call/holdcall:incomingQueue, call:status, call:hold
录音/记录/{accId}/call/record, /{accId}/call/recordStatus, /{accId}/meet/recordcall:recordNotify, call:recordStatusNotify, meeting:record
会议/{accId}/meet/status, /{accId}/meet/join, /{accId}/meet/leave, /{accId}/meet/memstsmeeting:status, meeting:member, meeting:list
会议扩展/meetScreenSetIndex, /meetMixScreen, /meetSplitScreen, /meetShareTelmeeting:screenSetIndex, meeting:mixScreen, meeting:splitScreen, meeting:shareTel
软手柄/shandlePreemptRegisterdispatch:softHandlerRegister

运行时可以查看后台订阅计划:

const { createBackendSubscriptionPlan } = require("@scooper/meeting-sdk/server");

console.table(createBackendSubscriptionPlan({ accountId: "1001" }));

Express + SSE 示例

const express = require("express");
const { createScooperBackendListener, createSseHandler } = require("@scooper/meeting-sdk/server");
const { createCometDTransport } = require("@scooper/meeting-sdk/server/transports");

const cometdClient = createYourCometDClientSomehow();

const listener = createScooperBackendListener({
accountId: "1001",
transport: createCometDTransport({
client: cometdClient,
url: "https://dispatch.example.com/dispatch-web/cometd",
requestHeaders: {
"Sc-Token": process.env.DISPATCH_TOKEN
}
})
});

listener.on("message", (message) => {
console.log("[dispatch notify]", message.channel, message.event);
});

await listener.start();

const app = express();
app.get("/scooper-events", createSseHandler(listener, {
eventName: "scooper-event",
heartbeatMs: 25000,
allowOrigin: "*"
}));
app.listen(3000);

前端接入:

const sdk = ScooperMeetingSDK.create({
backendListener: {
enabled: true,
transport: "sse",
url: "/scooper-events"
},
callbacks: {
onMeetingMember(member) {
console.log("成员变化", member);
},
onCallIncomingQueue(queue) {
console.log("呼入队列", queue);
}
}
});

await sdk.init();

WebSocket 转发示例

const http = require("http");
const WebSocket = require("ws");
const { createScooperBackendListener, attachWebSocketServer } = require("@scooper/meeting-sdk/server");

const listener = createScooperBackendListener({
accountId: "1001",
transport: yourTransport
});
await listener.start();

const server = http.createServer();
const wss = new WebSocket.Server({ server, path: "/scooper-events" });
attachWebSocketServer(listener, wss);
server.listen(3000);

前端配置:

const sdk = ScooperMeetingSDK.create({
backendListener: {
enabled: true,
transport: "websocket",
url: "ws://localhost:3000/scooper-events"
}
});

转发消息格式

SSE/WebSocket 发给前端的消息格式固定:

{
"source": "scooper-dispatch",
"event": "meeting:status",
"channel": "/1001/meet/status",
"accountId": "1001",
"payload": {
"meetId": "meet-001",
"status": "created"
},
"meta": {
"constKey": "MEET_STS",
"category": "meeting",
"description": "会场状态变化:新增、销毁、锁定、录音、编辑等"
},
"receivedAt": "2026-07-06T10:00:00.000Z"
}

SDK 会先触发:

callbacks.onBackendListenerMessage(message)
sdk.on(ScooperMeetingSDK.EVENT.BACKEND_LISTENER_MESSAGE, handler)

随后按 message.event 再触发业务事件:

callbacks.onMeetingStatus(message.payload)
sdk.on(ScooperMeetingSDK.EVENT.MEETING_STATUS, handler)

自定义 transport

如果第三方已有消息中间件,不想使用 CometD client,可以直接实现 transport:

const listener = createScooperBackendListener({
accountId: "1001",
transport: {
async connect() {},
async subscribe(channel, handler) {
const off = mq.subscribe(channel, handler);
return off;
},
async disconnect() {}
}
});

前端也支持自定义 transport:

const sdk = ScooperMeetingSDK.create({
backendListener: {
enabled: true,
transport: {
connect({ onOpen, onMessage, onClose }) {
const off = myBus.on("dispatch-message", onMessage);
onOpen();
return () => {
off();
onClose();
};
}
}
}
});

过滤订阅

只监听会议事件:

const listener = createScooperBackendListener({
accountId: "1001",
categories: ["meeting"],
transport
});

只监听指定 SDK 事件:

const listener = createScooperBackendListener({
accountId: "1001",
events: [
"meeting:status",
"meeting:member",
"call:status"
],
transport
});

对接注意事项

  • accountId 是订阅账号级 channel 必需参数,会被替换成 /{accId} 前缀。
  • 后台桥接建议只转发当前业务账号有权接收的 channel,不要把所有账号消息广播给所有前端。
  • SSE 不能自定义请求头,token 可以放在 query 中;生产环境建议后台校验当前登录态后再建立连接。
  • WebSocket/SSE 断线重连策略通常放在第三方网关或页面层;SDK 会通过 backend:errorbackend:disconnected 通知调用方。
  • 需要扩展定制 channel 时,在 extraEvents 中增加 { event, backendChannels, category, description },前端会收到同名 SDK 事件。