owpengram-server/internal/rpc/phone_push.go

123 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"encoding/hex"
"github.com/iamxvbaba/td/proto"
"github.com/iamxvbaba/td/tg"
"go.uber.org/zap"
"telesrv/internal/domain"
)
// 通话信令推送全部走无 pts 直推pushUserMessage信令有效期秒级离线设备
// 重连后经 getDifference 补收一条早已失效的来电毫无意义甚至有害。唯一带 pts 的
// 产物是结束后的 messageActionPhoneCall 服务消息P2走 outbox 补偿离线设备)。
// phoneCallUpdates 把 viewer 视角的 phoneCall 状态包成可直推的 Updates 容器。
func (r *Router) phoneCallUpdates(ctx context.Context, call domain.PhoneCall, viewerID int64) *tg.Updates {
return r.phoneCallUpdatesWith(ctx, tgPhoneCallForViewer(call, viewerID), call, viewerID)
}
func (r *Router) phoneCallUpdatesWith(ctx context.Context, view tg.PhoneCallClass, call domain.PhoneCall, viewerID int64) *tg.Updates {
return &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdatePhoneCall{PhoneCall: view}},
Users: r.tgUsersForIDs(ctx, viewerID, []int64{call.AdminID, call.ParticipantID}),
Chats: []tg.ChatClass{},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
// pushPhoneCall 把 call 当前状态按 targetUserID 视角推给其全部在线设备
// ctx 携带的发起设备会被 pushUserMessage 的 except 语义排除)。
func (r *Router) pushPhoneCall(ctx context.Context, targetUserID int64, call domain.PhoneCall, logMessage string) int {
sent := r.pushUserMessage(ctx, targetUserID, logMessage, r.phoneCallUpdates(ctx, call, targetUserID))
// 诊断:确认服务端把 phoneCall 状态推给了对端【几条】连接。sent==0 说明对端
// 全部连接都没收到(一接就断类问题的关键判据)。
if r.log != nil {
r.log.Info("push phoneCall",
zap.String("stage", logMessage),
zap.Int64("target_user_id", targetUserID),
zap.Int64("call_id", call.ID),
zap.Int("sent", sent),
)
}
return sent
}
// pushPhoneCallToDevice 只把 phoneCall 状态推给【发起呼叫的那台设备】(originating
// session),不广播到该用户的其它会话。
//
// ⚠ 为什么必须定向:一个「呼出」通话只属于发起它的那台设备。若把呼叫方视角的
// phoneCallWaiting(receive_date) 广播到该账号的所有会话,而【同一账号又登录在被叫
// 的那台手机上】(多账号同机),手机上这份呼叫方副本会收到 phoneCallWaiting——它与
// 来电是同一个 call_id于是覆写 VoIPService.callIShouldHavePutIntoIntent 这个
// 【静态全局】pending 来电stock DrKLOMessagesController 只按 call.id 匹配、不校验
// 账号),把带 g_a_hash 的 phoneCallRequested 换成不含 g_a_hash 的 phoneCallWaiting。
// 被叫接听后 SHA256(g_a)!=g_a_hash → 「Ga hash doesn't match」→ callFailed → 一接就断。
// 定向到 CallerDevice 后手机上的呼叫方副本收不到该更新pending 来电不被污染。
// See memory: call-ga-hash-multiaccount-clobber。
//
// 呼叫方发起会话在 requestCall 时已发过 RPC、必然 readyPushToSessionForAuthKey 对
// 暂未 ready 的会话也会入队补发不丢。CallerDevice 未知(理论上不会)时回退广播。
func (r *Router) pushPhoneCallToDevice(ctx context.Context, targetUserID int64, device domain.SessionRef, call domain.PhoneCall, logMessage string) {
if device.Zero() || r.deps.Sessions == nil {
r.pushPhoneCall(ctx, targetUserID, call, logMessage)
return
}
upd := r.phoneCallUpdates(ctx, call, targetUserID)
err := r.deps.Sessions.PushToSessionForAuthKey(ctx, device.RawAuthKeyID, device.SessionID, proto.MessageFromServer, upd)
if r.log != nil {
r.log.Info("push phoneCall to device",
zap.String("stage", logMessage),
zap.Int64("target_user_id", targetUserID),
zap.Int64("call_id", call.ID),
zap.String("device_auth_key", hex.EncodeToString(device.RawAuthKeyID[:])),
zap.Int64("device_session", device.SessionID),
zap.Error(err),
)
}
if err != nil {
// 定向失败(会话已不存在)才回退广播——正常路径不会走到,故不会重新引入污染。
r.pushPhoneCall(ctx, targetUserID, call, logMessage)
}
}
// pushPhoneCallStopRinging 向被叫其它设备推合成 phoneCallDiscarded 停振铃P0-1 修正)。
// ctx 必须是接听设备的请求上下文except 语义恰好把赢家排除在外。
func (r *Router) pushPhoneCallStopRinging(ctx context.Context, call domain.PhoneCall) int {
upd := r.phoneCallUpdatesWith(ctx, tgPhoneCallStopRinging(call), call, call.ParticipantID)
return r.pushUserMessage(ctx, call.ParticipantID, "phone call stop ringing", upd)
}
// pushPhoneCallDiscardedBoth 把终态推给双方全部设备(发起设备由 ctx except 排除,
// 其结果从 RPC 响应获得)。
func (r *Router) pushPhoneCallDiscardedBoth(ctx context.Context, call domain.PhoneCall) {
r.pushPhoneCall(ctx, call.AdminID, call, "phone call discarded")
r.pushPhoneCall(ctx, call.ParticipantID, call, "phone call discarded")
}
// pushPhoneSignalingData 把信令字节透传给对端。优先走设备锚点定向推送
// requestCall/acceptCall 受理设备),锚点失效(设备重连换 session则回退
// user 级扇出——非参与设备按 phone_call_id 不匹配静默丢弃TDesktop
// handleSignalingData 行为),扇出无害。
func (r *Router) pushPhoneSignalingData(ctx context.Context, targetUserID int64, device domain.SessionRef, callID int64, data []byte) {
upd := &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdatePhoneCallSignalingData{
PhoneCallID: callID,
Data: data,
}},
Users: []tg.UserClass{},
Chats: []tg.ChatClass{},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
if !device.Zero() && r.deps.Sessions != nil {
if err := r.deps.Sessions.PushToSessionForAuthKey(ctx, device.RawAuthKeyID, device.SessionID, proto.MessageFromServer, upd); err == nil {
return
}
}
r.pushUserMessage(ctx, targetUserID, "phone call signaling", upd)
}