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
|
|
@ -18,11 +18,19 @@ import (
|
|||
type Service struct {
|
||||
store store.SecretChatStore
|
||||
queue store.EncryptedQueueStore
|
||||
// channelParticipants is the per-device qts queue for channel-membership
|
||||
// self-notifications (kick/ban/promote/transfer). It shares its qts number
|
||||
// space with queue (one qts sequence per device, per the MTProto spec for
|
||||
// updateChannelParticipant) but is otherwise unrelated to secret chats;
|
||||
// it lives here only because this Service already owns the device qts
|
||||
// watermark plumbing.
|
||||
channelParticipants store.ChannelParticipantQueueStore
|
||||
}
|
||||
|
||||
// NewService 创建密聊服务。
|
||||
func NewService(st store.SecretChatStore, queue store.EncryptedQueueStore) *Service {
|
||||
return &Service{store: st, queue: queue}
|
||||
// NewService 创建密聊服务。channelParticipants may be nil in tests that don't
|
||||
// exercise channel-membership self-notifications.
|
||||
func NewService(st store.SecretChatStore, queue store.EncryptedQueueStore, channelParticipants store.ChannelParticipantQueueStore) *Service {
|
||||
return &Service{store: st, queue: queue, channelParticipants: channelParticipants}
|
||||
}
|
||||
|
||||
// RequestEncryption 受理 requestEncryption:校验 g_a → 校验 random_id/chat_id 全局唯一性 → 分配双
|
||||
|
|
@ -242,6 +250,34 @@ func (s *Service) AckQueue(ctx context.Context, deviceAuthKeyID int64, maxQts in
|
|||
return s.queue.AckEncryptedMessages(ctx, deviceAuthKeyID, maxQts)
|
||||
}
|
||||
|
||||
// AppendChannelParticipantEvent durably enqueues a channel-membership
|
||||
// self-notification for one device, reserving that device's next qts.
|
||||
func (s *Service) AppendChannelParticipantEvent(ctx context.Context, ev domain.DeviceChannelParticipantEvent) (domain.DeviceChannelParticipantEvent, error) {
|
||||
if s.channelParticipants == nil || ev.ReceiverAuthKeyID == 0 {
|
||||
return domain.DeviceChannelParticipantEvent{}, nil
|
||||
}
|
||||
return s.channelParticipants.AppendChannelParticipantEvent(ctx, ev)
|
||||
}
|
||||
|
||||
// ListChannelParticipantEventsSince returns a device's channel-membership
|
||||
// self-notifications with qts > sinceQts (getDifference catch-up).
|
||||
func (s *Service) ListChannelParticipantEventsSince(ctx context.Context, deviceAuthKeyID int64, sinceQts, limit int) ([]domain.DeviceChannelParticipantEvent, error) {
|
||||
if s.channelParticipants == nil || deviceAuthKeyID == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return s.channelParticipants.ListChannelParticipantEventsSince(ctx, deviceAuthKeyID, sinceQts, limit)
|
||||
}
|
||||
|
||||
// AckChannelParticipantEvents marks a device's channel-membership
|
||||
// self-notifications up to maxQts as delivered (GC only; confirmed_qts itself
|
||||
// advances via AckQueue against the shared watermark).
|
||||
func (s *Service) AckChannelParticipantEvents(ctx context.Context, deviceAuthKeyID int64, maxQts int) error {
|
||||
if s.channelParticipants == nil || deviceAuthKeyID == 0 || maxQts <= 0 {
|
||||
return nil
|
||||
}
|
||||
return s.channelParticipants.AckChannelParticipantEvents(ctx, deviceAuthKeyID, maxQts)
|
||||
}
|
||||
|
||||
// RecordEncryptionEvent 写入 durable updateEncryption 状态事件(离线补偿)。
|
||||
// targetAuthKeyID=0 表示账号级(建链前邀请/撤回对 target 所有设备可见),非 0 表示
|
||||
// 绑定设备定向。投递时按 secret_chats 权威态重建(不固化快照)。
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ func validGA() []byte {
|
|||
|
||||
func newTestService() (*Service, *memory.SecretChatStore) {
|
||||
st := memory.NewSecretChatStore()
|
||||
return NewService(st, memory.NewEncryptedQueueStore()), st
|
||||
queue := memory.NewEncryptedQueueStore()
|
||||
return NewService(st, queue, queue), st
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue