终止成员连接
频道授权控制订阅,但不会分配连接级别的身份。没有该身份,在其他地方退出登录不会关闭已有的 socket 或移除其频道订阅。
调用 signin() 从后端为连接分配一个 member_id。之后断开连接操作就可以关闭该成员在应用上持有的所有连接。
在远程退出登录、令牌撤销、封禁、密码更改或工作区席位移除后使用此流程。
1. 为连接签入身份
将客户端指向你自己后端的一个端点,然后调用一次 signin():
import { BirdRealtime } from "@messagebird/realtime";
const bird = new BirdRealtime({
appKey: "your-app-key",
region: "us1",
memberAuthEndpoint: "/bird/auth/member",
});
const member = await bird.signin();
console.log("signed in as", member.member_id);import BirdRealtime
let bird = BirdRealtime(options: .init(
appKey: "your-app-key",
region: "us1",
memberAuthEndpoint: URL(string: "https://your-backend.example.com/bird/auth/member")
))
let member = try await bird.signin()
print("signed in as", member.memberId)import com.bird.realtime.BirdRealtime
import com.bird.realtime.BirdRealtimeOptions
val bird = BirdRealtime(
BirdRealtimeOptions(
appKey = "your-app-key",
region = "us1",
memberAuthEndpoint = "https://your-backend.example.com/bird/auth/member",
)
)
val member = bird.signin() // suspending
println("signed in as ${member.memberId}")身份属于连接。调用一次 signin(),客户端会在每次重连后重新签入。
签入不会授权频道订阅。私有频道和 presence 频道仍然会调用你的频道授权端点。没有成员身份的连接可以订阅该端点批准的任何频道。参见频道授权。
2. 从后端签署身份
客户端通过 POST 请求发送连接 ID:
代码示例
{ "connection_id": "26896.319537" }你的端点以 JSON 字符串形式返回身份,加上对该字符串的签名:
代码示例
{
"auth": "your-app-key:8f9a…",
"member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Ada\"}}"
}待签名的字符串是 <connection_id>::member::<member_data>,member 两侧各有两个冒号。用应用密钥对其进行 HMAC-SHA256 运算,十六进制编码,并加上应用 key 前缀:
import { createHmac } from "node:crypto";
app.post("/bird/auth/member", (req, res) => {
const { connection_id } = req.body;
// Your own decision: who is this caller, and may they connect at all?
const user = getUserFromSession(req);
if (!user) return res.sendStatus(403);
const memberData = JSON.stringify({
member_id: user.id,
member_info: { name: user.name },
});
const sig = createHmac("sha256", process.env.BIRD_REALTIME_SECRET)
.update(`${connection_id}::member::${memberData}`)
.digest("hex");
res.json({ auth: `${process.env.BIRD_REALTIME_KEY}:${sig}`, member_data: memberData });
});import hmac, hashlib, json, os
def sign_in(connection_id: str, user) -> dict:
key = os.environ["BIRD_REALTIME_KEY"]
secret = os.environ["BIRD_REALTIME_SECRET"].encode()
member_data = json.dumps({"member_id": user.id, "member_info": {"name": user.name}})
to_sign = f"{connection_id}::member::{member_data}"
sig = hmac.new(secret, to_sign.encode(), hashlib.sha256).hexdigest()
return {"auth": f"{key}:{sig}", "member_data": member_data}func signIn(connectionID string, user User) (map[string]string, error) {
key := os.Getenv("BIRD_REALTIME_KEY")
secret := []byte(os.Getenv("BIRD_REALTIME_SECRET"))
memberData, err := json.Marshal(map[string]any{
"member_id": user.ID,
"member_info": map[string]string{"name": user.Name},
})
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(connectionID + "::member::" + string(memberData)))
return map[string]string{
"auth": key + ":" + hex.EncodeToString(mac.Sum(nil)),
"member_data": string(memberData),
}, nil
}function signIn(string $connectionId, User $user): array
{
$key = getenv('BIRD_REALTIME_KEY');
$secret = getenv('BIRD_REALTIME_SECRET');
$memberData = json_encode(['member_id' => $user->id, 'member_info' => ['name' => $user->name]]);
$sig = hash_hmac('sha256', "{$connectionId}::member::{$memberData}", $secret);
return ['auth' => "{$key}:{$sig}", 'member_data' => $memberData];
}对你返回的 member_data 字符串原样签名。重新序列化对象可能改变其字节并使签名失效。
member_id 是断开连接操作的目标值。它接受最多 128 个 URL 安全字符,包括 + : @ . _ -,但不包括 / ? # % 或空白字符。如果你的标识符包含不支持的字符,请在签名前将其映射为稳定的安全值。边缘节点会拒绝无效的成员 ID,使连接没有可被断开连接操作定位的身份。
member_info 是可选的。在连接签入时,它返回给该客户端,而不是广播到 presence 频道。
此处签名的字符串与 presence 订阅签名的字符串故意不同,因此 presence 授权永远无法被重放来冒领身份,即使两种载荷都叫做 member_data。
3. 断开成员连接
await bird.realtime.members.disconnect("rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42");client.realtime.members.disconnect("rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42")if err := client.Realtime.Members.Disconnect(context.Background(), "rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42"); err != nil {
log.Fatal(err)
}$bird->realtime->members->disconnect('rap_01krdgeqcxet5s7t44vh8rt9mg', 'u_42');curl -X POST \
https://us1.platform.bird.com/v1/realtime/apps/rap_01krdgeqcxet5s7t44vh8rt9mg/members/u_42/disconnect \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "X-Realtime-Key: $BIRD_REALTIME_KEY" \
-H "X-Realtime-Secret: $BIRD_REALTIME_SECRET"该操作会关闭该成员在应用上的所有已签入连接,包括其他标签页和设备。它不会关闭匿名连接或分配给其他成员的连接。
客户端看到的情况
连接以代码 4009 关闭,且不会自动重连:
bird.connection.bind("error", (e) => {
if (e.code === 4009) showSignedOutScreen();
});bird.onError { error in
if error.code == 4009 { showSignedOutScreen() }
}bird.onError { error ->
if (error.code == 4009) showSignedOutScreen()
}认证结束后显示已退出状态。调用 bird.connect() 会启动一个新连接并通过你的端点签入,因此只在成员的授权状态发生变化后才重连。
签入失败不会关闭 socket。客户端会单独报告失败,并使连接保持无成员身份状态。重连后,客户端通过 signin_error 报告此情况,因为原始的 signin() promise 已经 resolve。
bird.connection.bind("signin_error", (e) => {
console.warn("connection has no identity:", e.message);
});bird.onSigninError { error in
print("connection has no identity:", error.message)
}bird.onSigninError { error ->
println("connection has no identity: ${error.message}")
}连接仍然可以接收已授权频道的事件,但成员事件和断开连接操作无法定位它。在浏览器客户端中,此状态下 bird.signedInMember 为 null。
阻止重连
断开连接操作关闭当前连接,但不会创建持久性阻止。同一客户端可以再次连接。
在断开成员连接之前撤销会话或记录封禁。这样,如果客户端重连,你的成员授权端点可以返回 403 Forbidden。
后续步骤
- 频道授权介绍了后端计算的另一个签名,用于私有频道和 presence 频道的订阅。
- 断开成员连接是该请求的完整 API 参考。
- 向成员发送事件是另一个面向成员的操作。
- Presence 频道解释了为什么一个成员可以持有多个连接。
相关资源
继续查阅此主题的文档、指南和示例。资源为英文。