channels: give kicked/banned/promoted/transferred users a real qts so their client applies it
updateChannelParticipant carries the account's qts per the MTProto spec, but the server always sent Qts: 0, so real clients silently discarded it as a stale duplicate -- the banned/kicked user's channel never vanished locally and no correct "removed by admin" message showed, even though the update was delivered successfully at the transport layer. Add a durable per-device qts queue (channel_participant_event_queue) sharing its qts number space with the existing secret-chat queue (one qts sequence per device, per spec), and use it to stamp a correct, monotonically increasing qts on the update for every device of the affected user -- for channel bans/kicks, admin promotion/demotion, and ownership transfer. A device offline when it happened can now recover the event via updates.getDifference instead of missing it permanently.
This commit is contained in:
parent
97711c9d2e
commit
206bde18e0
16 changed files with 482 additions and 42 deletions
|
|
@ -3,6 +3,7 @@ package rpc
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/iamxvbaba/td/proto"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
|
|
@ -103,6 +104,10 @@ func (r *Router) onMessagesReceivedQueue(ctx context.Context, maxQts int) ([]int
|
|||
if err := r.deps.SecretChats.AckQueue(ctx, deviceKey, maxQts); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
// Best-effort GC mark for the sibling channel-participant-event queue,
|
||||
// which shares this device's qts sequence; a failure here doesn't affect
|
||||
// correctness (only retention), so it isn't fatal to the RPC.
|
||||
_ = r.deps.SecretChats.AckChannelParticipantEvents(ctx, deviceKey, maxQts)
|
||||
return []int64{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -143,38 +148,84 @@ func (r *Router) deviceEncryptedQts(ctx context.Context) int {
|
|||
return qts
|
||||
}
|
||||
|
||||
// encryptedDifference 返回当前设备 qts > sinceQts 的连续前缀、推进后的 qts 与是否还有
|
||||
// 下一页。存储错误或 qts gap 必须 fail-fast,禁止越过缺口推进客户端水位。
|
||||
func (r *Router) encryptedDifference(ctx context.Context, sinceQts int) ([]tg.EncryptedMessageClass, int, bool, error) {
|
||||
// encryptedDifference 返回当前设备 qts > sinceQts 的连续前缀(加密消息 +
|
||||
// channel 成员关系自通知,二者共用同一设备 qts 序列,按 qts 归并后统一做缺口
|
||||
// 检查)、推进后的 qts 与是否还有下一页。存储错误或 qts gap 必须 fail-fast,
|
||||
// 禁止越过缺口推进客户端水位。
|
||||
func (r *Router) encryptedDifference(ctx context.Context, sinceQts int) ([]tg.EncryptedMessageClass, []tg.UpdateClass, []int64, []int64, int, bool, error) {
|
||||
if r.deps.SecretChats == nil {
|
||||
return nil, sinceQts, false, nil
|
||||
return nil, nil, nil, nil, sinceQts, false, nil
|
||||
}
|
||||
deviceKey, ok := businessAuthKeyIDFrom(ctx)
|
||||
if !ok {
|
||||
return nil, sinceQts, false, nil
|
||||
return nil, nil, nil, nil, sinceQts, false, nil
|
||||
}
|
||||
msgs, err := r.deps.SecretChats.ListNewMessages(ctx, deviceKey, sinceQts, encryptedDifferencePageSize+1)
|
||||
if err != nil {
|
||||
return nil, sinceQts, false, err
|
||||
return nil, nil, nil, nil, sinceQts, false, err
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return nil, sinceQts, false, nil
|
||||
participantEvents, err := r.deps.SecretChats.ListChannelParticipantEventsSince(ctx, deviceKey, sinceQts, encryptedDifferencePageSize+1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, sinceQts, false, err
|
||||
}
|
||||
partial := len(msgs) > encryptedDifferencePageSize
|
||||
if partial {
|
||||
msgs = msgs[:encryptedDifferencePageSize]
|
||||
if len(msgs) == 0 && len(participantEvents) == 0 {
|
||||
return nil, nil, nil, nil, sinceQts, false, nil
|
||||
}
|
||||
out := make([]tg.EncryptedMessageClass, 0, len(msgs))
|
||||
items := make([]deviceQtsItem, 0, len(msgs)+len(participantEvents))
|
||||
for i := range msgs {
|
||||
items = append(items, deviceQtsItem{qts: msgs[i].Qts, msg: &msgs[i]})
|
||||
}
|
||||
for i := range participantEvents {
|
||||
items = append(items, deviceQtsItem{qts: participantEvents[i].Qts, participant: &participantEvents[i]})
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].qts < items[j].qts })
|
||||
partial := len(msgs) > encryptedDifferencePageSize || len(participantEvents) > encryptedDifferencePageSize
|
||||
if len(items) > encryptedDifferencePageSize {
|
||||
items = items[:encryptedDifferencePageSize]
|
||||
partial = true
|
||||
}
|
||||
encMsgs := make([]tg.EncryptedMessageClass, 0, len(items))
|
||||
participantUpdates := make([]tg.UpdateClass, 0, len(items))
|
||||
participantPeerIDs := make([]int64, 0, len(items))
|
||||
participantChannelIDs := make([]int64, 0, len(items))
|
||||
newQts := sinceQts
|
||||
for i, m := range msgs {
|
||||
for i, item := range items {
|
||||
expected := newQts + 1
|
||||
if m.Qts != expected {
|
||||
return nil, sinceQts, false, fmt.Errorf("secret chat qts gap at index %d: got %d want %d", i, m.Qts, expected)
|
||||
if item.qts != expected {
|
||||
return nil, nil, nil, nil, sinceQts, false, fmt.Errorf("device qts gap at index %d: got %d want %d", i, item.qts, expected)
|
||||
}
|
||||
out = append(out, tgEncryptedMessage(m))
|
||||
newQts = m.Qts
|
||||
if item.msg != nil {
|
||||
encMsgs = append(encMsgs, tgEncryptedMessage(*item.msg))
|
||||
} else {
|
||||
ev := item.participant
|
||||
update := &tg.UpdateChannelParticipant{
|
||||
ChannelID: ev.ChannelID,
|
||||
Date: ev.Date,
|
||||
ActorID: ev.ActorUserID,
|
||||
UserID: ev.Participant.UserID,
|
||||
Qts: ev.Qts,
|
||||
}
|
||||
if ev.Previous.UserID != 0 {
|
||||
update.SetPrevParticipant(tgChannelParticipantForUpdate(ev.ReceiverUserID, ev.Previous))
|
||||
}
|
||||
if ev.Participant.UserID != 0 {
|
||||
update.SetNewParticipant(tgChannelParticipantForUpdate(ev.ReceiverUserID, ev.Participant))
|
||||
}
|
||||
participantUpdates = append(participantUpdates, update, &tg.UpdateChannel{ChannelID: ev.ChannelID})
|
||||
participantPeerIDs = append(participantPeerIDs, ev.ActorUserID, ev.Participant.UserID, ev.Participant.InviterUserID, ev.Previous.UserID, ev.Previous.InviterUserID)
|
||||
participantChannelIDs = append(participantChannelIDs, ev.ChannelID)
|
||||
}
|
||||
newQts = item.qts
|
||||
}
|
||||
return out, newQts, partial, nil
|
||||
return encMsgs, participantUpdates, participantPeerIDs, participantChannelIDs, newQts, partial, nil
|
||||
}
|
||||
|
||||
// deviceQtsItem is one entry in a device's merged qts stream: either an
|
||||
// encrypted message or a channel-participant self-notification, never both.
|
||||
type deviceQtsItem struct {
|
||||
qts int
|
||||
msg *domain.SecretChatMessage
|
||||
participant *domain.DeviceChannelParticipantEvent
|
||||
}
|
||||
|
||||
// injectEncryptedMessages 把加密消息与推进后的 qts 注入差分响应(按类型分别写 State /
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue