github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/push.go (about) 1 package chat 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "sync" 9 "time" 10 11 "strings" 12 13 "github.com/keybase/client/go/chat/globals" 14 "github.com/keybase/client/go/chat/pager" 15 "github.com/keybase/client/go/chat/storage" 16 "github.com/keybase/client/go/chat/types" 17 "github.com/keybase/client/go/chat/utils" 18 "github.com/keybase/client/go/gregor" 19 "github.com/keybase/client/go/libkb" 20 "github.com/keybase/client/go/protocol/chat1" 21 "github.com/keybase/client/go/protocol/gregor1" 22 "github.com/keybase/client/go/protocol/keybase1" 23 "github.com/keybase/clockwork" 24 "github.com/keybase/go-codec/codec" 25 "golang.org/x/net/context" 26 "golang.org/x/sync/errgroup" 27 ) 28 29 var errPushOrdererMissingLatestInboxVersion = errors.New("no latest inbox version") 30 31 type messageWaiterEntry struct { 32 vers chat1.InboxVers 33 cb chan struct{} 34 } 35 36 type gregorMessageOrderer struct { 37 globals.Contextified 38 utils.DebugLabeler 39 sync.Mutex 40 41 clock clockwork.Clock 42 waiters map[string][]messageWaiterEntry 43 } 44 45 func newGregorMessageOrderer(g *globals.Context) *gregorMessageOrderer { 46 return &gregorMessageOrderer{ 47 Contextified: globals.NewContextified(g), 48 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "gregorMessageOrderer", false), 49 waiters: make(map[string][]messageWaiterEntry), 50 clock: clockwork.NewRealClock(), 51 } 52 } 53 54 func (g *gregorMessageOrderer) msgKey(uid gregor1.UID, vers chat1.InboxVers) string { 55 return fmt.Sprintf("%s:%d", uid, vers) 56 } 57 58 func (g *gregorMessageOrderer) isUIDKey(key string, uid gregor1.UID) bool { 59 toks := strings.Split(key, ":") 60 return toks[0] == uid.String() 61 } 62 63 func (g *gregorMessageOrderer) latestInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) { 64 ibox := storage.NewInbox(g.G()) 65 vers, err := ibox.Version(ctx, uid) 66 if err != nil { 67 return 0, err 68 } 69 if vers == 0 { 70 return 0, errPushOrdererMissingLatestInboxVersion 71 } 72 return vers, nil 73 } 74 75 func (g *gregorMessageOrderer) addToWaitersLocked(ctx context.Context, uid gregor1.UID, storedVers, 76 msgVers chat1.InboxVers) (res []messageWaiterEntry) { 77 for i := storedVers + 1; i < msgVers; i++ { 78 entry := messageWaiterEntry{ 79 vers: msgVers, 80 cb: make(chan struct{}), 81 } 82 res = append(res, entry) 83 key := g.msgKey(uid, i) 84 g.waiters[key] = append(g.waiters[key], entry) 85 } 86 return res 87 } 88 89 func (g *gregorMessageOrderer) waitOnWaiters(ctx context.Context, vers chat1.InboxVers, 90 waiters []messageWaiterEntry) (res chan struct{}) { 91 res = make(chan struct{}) 92 go func(ctx context.Context) { 93 for _, w := range waiters { 94 select { 95 case <-w.cb: 96 case <-ctx.Done(): 97 g.Debug(ctx, "waitOnWaiters: context cancelled: waiter: %d target: %d", vers, w.vers) 98 } 99 } 100 close(res) 101 }(globals.BackgroundChatCtx(ctx, g.G())) 102 return res 103 } 104 105 func (g *gregorMessageOrderer) cleanupAfterTimeoutLocked(uid gregor1.UID, vers chat1.InboxVers) { 106 for k, v := range g.waiters { 107 if g.isUIDKey(k, uid) { 108 var newv []messageWaiterEntry 109 for _, w := range v { 110 if w.vers != vers { 111 newv = append(newv, w) 112 } 113 } 114 if len(newv) == 0 { 115 delete(g.waiters, k) 116 } else { 117 g.waiters[k] = newv 118 } 119 } 120 } 121 } 122 123 func (g *gregorMessageOrderer) WaitForTurn(ctx context.Context, uid gregor1.UID, 124 newVers chat1.InboxVers) (res chan struct{}) { 125 res = make(chan struct{}) 126 // Out of order update, we are going to wait a fixed amount of time for the correctly 127 // ordered update 128 deadline := g.clock.Now().Add(time.Second) 129 go func(ctx context.Context) { 130 defer close(res) 131 g.Lock() 132 var dur time.Duration 133 vers, err := g.latestInboxVersion(ctx, uid) 134 if err != nil { 135 if newVers >= 2 { 136 // If we fail to get the inbox version, then the general goal is to just simulate like 137 // we are looking for the previous update to the one we just got (newVers). In order to do 138 // this, we act like our previous inbox version was just missing the inbox version one less 139 // than newVers. That means we are waiting for this single update (which probably isn't 140 // coming, we usually get here if we miss the inbox cache on disk). 141 vers = newVers - 2 142 } else { 143 vers = 0 144 } 145 g.Debug(ctx, "WaitForTurn: failed to get current inbox version: %s, proceeding with vers %d", 146 err, vers) 147 } 148 // add extra time if we are multiple updates behind 149 dur = time.Duration(newVers-vers-1) * time.Second 150 if dur < 0 { 151 dur = 0 152 } 153 // cap at a minute 154 if dur > time.Minute { 155 dur = time.Minute 156 } 157 158 deadline = deadline.Add(dur) 159 waiters := g.addToWaitersLocked(ctx, uid, vers, newVers) 160 g.Unlock() 161 if len(waiters) == 0 { 162 return 163 } 164 waitBegin := g.clock.Now() 165 g.Debug(ctx, 166 "WaitForTurn: out of order update received, waiting on %d updates: vers: %d newVers: %d dur: %v", 167 len(waiters), vers, newVers, dur+time.Second) 168 ctx, cancel := context.WithCancel(ctx) 169 defer cancel() 170 select { 171 case <-g.waitOnWaiters(ctx, newVers, waiters): 172 g.Debug(ctx, "WaitForTurn: cleared by earlier messages: vers: %d wait: %v", newVers, 173 g.clock.Now().Sub(waitBegin)) 174 case <-g.clock.AfterTime(deadline): 175 g.Debug(ctx, "WaitForTurn: timeout reached, charging forward: vers: %d wait: %v", newVers, 176 g.clock.Now().Sub(waitBegin)) 177 g.Lock() 178 g.cleanupAfterTimeoutLocked(uid, newVers) 179 g.Unlock() 180 } 181 }(globals.BackgroundChatCtx(ctx, g.G())) 182 return res 183 } 184 185 func (g *gregorMessageOrderer) CompleteTurn(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers) { 186 g.Lock() 187 defer g.Unlock() 188 key := g.msgKey(uid, vers) 189 waiters := g.waiters[key] 190 if len(waiters) > 0 { 191 g.Debug(ctx, "CompleteTurn: clearing %d messages on vers %d", len(waiters), vers) 192 } 193 for _, w := range waiters { 194 close(w.cb) 195 } 196 delete(g.waiters, key) 197 } 198 199 func (g *gregorMessageOrderer) SetClock(clock clockwork.Clock) { 200 g.clock = clock 201 } 202 203 type PushHandler struct { 204 globals.Contextified 205 utils.DebugLabeler 206 sync.Mutex 207 startMu sync.Mutex 208 eg errgroup.Group 209 started bool 210 211 identNotifier types.IdentifyNotifier 212 orderer *gregorMessageOrderer 213 typingMonitor *TypingMonitor 214 215 // testing only 216 testingIgnoreBroadcasts bool 217 } 218 219 func NewPushHandler(g *globals.Context) *PushHandler { 220 p := &PushHandler{ 221 Contextified: globals.NewContextified(g), 222 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "PushHandler", false), 223 identNotifier: NewCachingIdentifyNotifier(g), 224 orderer: newGregorMessageOrderer(g), 225 typingMonitor: NewTypingMonitor(g), 226 } 227 p.identNotifier.ResetOnGUIConnect() 228 return p 229 } 230 231 func (g *PushHandler) Start(ctx context.Context, _ gregor1.UID) { 232 defer g.Trace(ctx, nil, "Start")() 233 g.startMu.Lock() 234 defer g.startMu.Unlock() 235 if g.started { 236 return 237 } 238 g.started = true 239 } 240 241 func (g *PushHandler) Stop(ctx context.Context) chan struct{} { 242 defer g.Trace(ctx, nil, "Stop")() 243 g.startMu.Lock() 244 defer g.startMu.Unlock() 245 ch := make(chan struct{}) 246 if g.started { 247 g.started = false 248 go func() { 249 _ = g.eg.Wait() 250 close(ch) 251 }() 252 } else { 253 close(ch) 254 } 255 return ch 256 } 257 258 func (g *PushHandler) SetClock(clock clockwork.Clock) { 259 g.orderer.SetClock(clock) 260 } 261 262 func (g *PushHandler) TlfFinalize(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 263 defer g.Trace(ctx, &err, "TlfFinalize")() 264 if m.Body() == nil { 265 return errors.New("gregor handler for chat.tlffinalize: nil message body") 266 } 267 var identBreaks []keybase1.TLFIdentifyFailure 268 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 269 g.identNotifier) 270 271 var update chat1.TLFFinalizeUpdate 272 reader := bytes.NewReader(m.Body().Bytes()) 273 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 274 if err = dec.Decode(&update); err != nil { 275 return err 276 } 277 uid := gregor1.UID(m.UID().Bytes()) 278 279 // Order updates based on inbox version of the update from the server 280 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 281 f := func(ctx context.Context) (err error) { 282 defer g.Trace(ctx, &err, "TlfFinalize(goroutine)")() 283 <-cb 284 g.Lock() 285 defer g.Unlock() 286 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 287 288 // Update inbox 289 var convs []chat1.ConversationLocal 290 if convs, err = g.G().InboxSource.TlfFinalize(ctx, m.UID().Bytes(), update.InboxVers, 291 update.ConvIDs, update.FinalizeInfo); err != nil { 292 g.Debug(ctx, "tlf finalize: unable to update inbox: %v", err.Error()) 293 } 294 convMap := make(map[chat1.ConvIDStr]chat1.ConversationLocal) 295 for _, conv := range convs { 296 convMap[conv.GetConvID().ConvIDStr()] = conv 297 } 298 299 // Send notify for each conversation ID 300 for _, convID := range update.ConvIDs { 301 var conv *chat1.ConversationLocal 302 if mapConv, ok := convMap[convID.ConvIDStr()]; ok { 303 conv = &mapConv 304 } else { 305 conv = nil 306 } 307 topicType := chat1.TopicType_NONE 308 if conv != nil { 309 topicType = conv.GetTopicType() 310 } 311 g.G().ActivityNotifier.TLFFinalize(ctx, uid, 312 convID, topicType, update.FinalizeInfo, g.presentUIItem(ctx, conv, uid, 313 utils.PresentParticipantsModeInclude)) 314 } 315 return nil 316 } 317 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 318 319 return nil 320 } 321 322 func (g *PushHandler) TlfResolve(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 323 defer g.Trace(ctx, &err, "TlfResolve")() 324 if m.Body() == nil { 325 return errors.New("gregor handler for chat.tlfresolve: nil message body") 326 } 327 var identBreaks []keybase1.TLFIdentifyFailure 328 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 329 g.identNotifier) 330 331 var update chat1.TLFResolveUpdate 332 reader := bytes.NewReader(m.Body().Bytes()) 333 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 334 if err = dec.Decode(&update); err != nil { 335 return err 336 } 337 uid := gregor1.UID(m.UID().Bytes()) 338 339 // Order updates based on inbox version of the update from the server 340 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 341 f := func(ctx context.Context) (err error) { 342 defer g.Trace(ctx, &err, "TlfResolve(goroutine)")() 343 <-cb 344 g.Lock() 345 defer g.Unlock() 346 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 347 // Get and localize the conversation to get the new tlfname. 348 inbox, _, err := g.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 349 types.InboxSourceDataSourceAll, nil, 350 &chat1.GetInboxLocalQuery{ 351 ConvIDs: []chat1.ConversationID{update.ConvID}, 352 }) 353 if err != nil { 354 g.Debug(ctx, "resolve: unable to read conversation: %v", err.Error()) 355 return 356 } 357 if len(inbox.Convs) != 1 { 358 g.Debug(ctx, "resolve: unable to find conversation, found: %d, expected 1", len(inbox.Convs)) 359 return 360 } 361 updateConv := inbox.Convs[0] 362 363 resolveInfo := chat1.ConversationResolveInfo{ 364 NewTLFName: updateConv.Info.TlfName, 365 } 366 g.Debug(ctx, "TlfResolve: convID: %s new TLF name: %s", updateConv.GetConvID(), 367 updateConv.Info.TlfName) 368 g.G().ActivityNotifier.TLFResolve(ctx, uid, 369 update.ConvID, updateConv.GetTopicType(), resolveInfo) 370 return nil 371 } 372 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 373 374 return nil 375 } 376 377 // squashChanMention will silence a chanMention if sent by a non-admin on a 378 // large (> chat1.MaxChanMentionConvSize member) team. The server drives this 379 // for mobile push notifications, client checks this for desktop notifications 380 func (g *PushHandler) squashChanMention(ctx context.Context, conv *chat1.ConversationLocal, 381 mvalid chat1.MessageUnboxedValid, untrustedTeamRole keybase1.TeamRole) (bool, error) { 382 // Verify the chanMention is for a TEAM that is larger than 383 // MaxChanMentionConvSize 384 if conv == nil || 385 conv.Info.MembersType != chat1.ConversationMembersType_TEAM || 386 len(conv.Info.Participants) < chat1.MaxChanMentionConvSize { 387 return false, nil 388 } 389 // If the sender is not an admin, large squash the mention. 390 // We use the server trust team role here since notifications themselves 391 // are based on server trust. 392 return !untrustedTeamRole.IsOrAbove(keybase1.TeamRole_ADMIN), nil 393 } 394 395 func (g *PushHandler) shouldDisplayDesktopNotification(ctx context.Context, 396 uid gregor1.UID, conv *chat1.ConversationLocal, 397 msg chat1.MessageUnboxed, untrustedTeamRole keybase1.TeamRole) bool { 398 399 if conv == nil || conv.Notifications == nil { 400 return false 401 } 402 if !utils.GetConversationStatusBehavior(conv.Info.Status).DesktopNotifications { 403 return false 404 } 405 if msg.IsValid() { 406 // No notifications for our own messages 407 if msg.Valid().ClientHeader.Sender.Eq(uid) { 408 return false 409 } 410 body := msg.Valid().MessageBody 411 typ, err := body.MessageType() 412 if err != nil { 413 g.Debug(ctx, "shouldDisplayDesktopNotification: failed to get message type: %v", err) 414 return false 415 } 416 apptype := keybase1.DeviceType_DESKTOP 417 kind := chat1.NotificationKind_GENERIC 418 if utils.IsNotifiableChatMessageType(typ, msg.Valid().AtMentions, msg.Valid().ChannelMention) { 419 // Check to make sure this is an eligible reaction message 420 if msg.GetMessageType() == chat1.MessageType_REACTION { 421 g.Debug(ctx, "shouldDisplayDesktopNotification: checking reaction") 422 supersedes, err := utils.GetSupersedes(msg) 423 if err != nil || len(supersedes) == 0 { 424 g.Debug(ctx, "shouldDisplayDesktopNotification: failed to get supersedes id from reaction, skipping: %v", err) 425 return false 426 } 427 supersedesMsg, err := g.G().ConvSource.GetMessages(ctx, conv.GetConvID(), uid, supersedes, 428 nil, nil, false) 429 if err != nil || len(supersedesMsg) == 0 || !supersedesMsg[0].IsValid() { 430 g.Debug(ctx, "shouldDisplayDesktopNotification: failed to get supersedes message from reaction, skipping: %v", err) 431 return false 432 } 433 if !supersedesMsg[0].Valid().ClientHeader.Sender.Eq(uid) { 434 g.Debug(ctx, "shouldDisplayDesktopNotification: skipping reaction post, not sender") 435 return false 436 } 437 } 438 439 // Check for generic hit on desktop right off and return true if we hit 440 if conv.Notifications.Settings[apptype][kind] { 441 return true 442 } 443 for _, at := range msg.Valid().AtMentions { 444 if at.Eq(uid) { 445 kind = chat1.NotificationKind_ATMENTION 446 break 447 } 448 } 449 chanMention := msg.Valid().ChannelMention 450 notifyFromChanMention := false 451 switch chanMention { 452 case chat1.ChannelMention_HERE, chat1.ChannelMention_ALL: 453 notifyFromChanMention = conv.Notifications.ChannelWide 454 shouldSquash, err := g.squashChanMention(ctx, conv, msg.Valid(), untrustedTeamRole) 455 if err != nil { 456 g.Debug(ctx, "shouldDisplayDesktopNotification: failed to squashChanMention: %v", err) 457 } 458 if shouldSquash { 459 g.Debug(ctx, "shouldDisplayDesktopNotification: squashing channel mention from untrustedTeamRole: %v", untrustedTeamRole) 460 return false 461 } 462 } 463 return conv.Notifications.Settings[apptype][kind] || notifyFromChanMention 464 } 465 } 466 return false 467 } 468 469 func (g *PushHandler) presentUIItem(ctx context.Context, conv *chat1.ConversationLocal, uid gregor1.UID, 470 partMode utils.PresentParticipantsMode) (res *chat1.InboxUIItem) { 471 if conv != nil { 472 return PresentConversationLocalWithFetchRetry(ctx, g.G(), uid, *conv, partMode) 473 } 474 return res 475 } 476 477 func (g *PushHandler) getSupersedesTarget(ctx context.Context, uid gregor1.UID, 478 conv *chat1.ConversationLocal, msg chat1.MessageUnboxed) (res *chat1.UIMessage) { 479 if !msg.IsValid() || conv == nil { 480 return nil 481 } 482 switch msg.GetMessageType() { 483 case chat1.MessageType_EDIT: 484 targetMsgID := msg.Valid().MessageBody.Edit().MessageID 485 msgs, err := g.G().ConvSource.GetMessages(ctx, conv.GetConvID(), uid, []chat1.MessageID{targetMsgID}, 486 nil, nil, false) 487 if err != nil { 488 g.Debug(ctx, "getSupersedesTarget: failed to get message: %v", err) 489 return nil 490 } 491 maxDeletedUpTo := conv.GetMaxDeletedUpTo() 492 msgs, err = g.G().ConvSource.TransformSupersedes(ctx, conv.GetConvID(), uid, msgs, nil, nil, nil, 493 &maxDeletedUpTo) 494 if err != nil || len(msgs) == 0 { 495 g.Debug(ctx, "getSupersedesTarget: failed to get xform'd message: %v", err) 496 return nil 497 } 498 uiMsg := utils.PresentMessageUnboxed(ctx, g.G(), msgs[0], uid, conv.GetConvID()) 499 return &uiMsg 500 default: 501 return nil 502 } 503 } 504 505 func (g *PushHandler) getReplyMessage(ctx context.Context, uid gregor1.UID, conv *chat1.ConversationLocal, 506 msg chat1.MessageUnboxed) (chat1.MessageUnboxed, error) { 507 if conv == nil { 508 return msg, nil 509 } 510 return NewReplyFiller(g.G()).FillSingle(ctx, uid, conv.GetConvID(), msg) 511 } 512 513 func (g *PushHandler) Activity(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 514 var identBreaks []keybase1.TLFIdentifyFailure 515 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 516 g.identNotifier) 517 defer g.Trace(ctx, &err, "Activity")() 518 if m.Body() == nil { 519 return errors.New("gregor handler for chat.activity: nil message body") 520 } 521 522 // Decode into generic form 523 var gm chat1.GenericPayload 524 uid := m.UID().Bytes() 525 reader := bytes.NewReader(m.Body().Bytes()) 526 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 527 if err = dec.Decode(&gm); err != nil { 528 g.Debug(ctx, "chat activity: failed to decode into generic payload: %v", err) 529 return err 530 } 531 convID := gm.ConvID 532 g.Debug(ctx, "chat activity: action %s vers: %d convID: %s", gm.Action, gm.InboxVers, convID) 533 534 // Order updates based on inbox version of the update from the server 535 cb := g.orderer.WaitForTurn(ctx, uid, gm.InboxVers) 536 f := func(ctx context.Context) (err error) { 537 defer g.Trace(ctx, &err, "Activity(goroutine)")() 538 <-cb 539 g.Lock() 540 defer g.Unlock() 541 defer g.orderer.CompleteTurn(ctx, uid, gm.InboxVers) 542 543 var activity *chat1.ChatActivity 544 var conv *chat1.ConversationLocal 545 action := gm.Action 546 reader.Reset(m.Body().Bytes()) 547 switch action { 548 case types.ActionNewMessage: 549 var nm chat1.NewMessagePayload 550 if err = dec.Decode(&nm); err != nil { 551 g.Debug(ctx, "chat activity: error decoding newMessage: %v", err) 552 return err 553 } 554 g.Debug(ctx, "chat activity: newMessage: convID: %s sender: %s msgID: %d typ: %v, untrustedTeamRole: %v", 555 nm.ConvID, nm.Message.ClientHeader.Sender, nm.Message.GetMessageID(), 556 nm.Message.GetMessageType(), nm.UntrustedTeamRole) 557 if nm.Message.ClientHeader.OutboxID != nil { 558 g.Debug(ctx, "chat activity: newMessage: outboxID: %s", 559 hex.EncodeToString(*nm.Message.ClientHeader.OutboxID)) 560 } else { 561 g.Debug(ctx, "chat activity: newMessage: outboxID is empty") 562 } 563 564 // Coin flip manager can completely handle the incoming message 565 if g.G().CoinFlipManager.MaybeInjectFlipMessage(ctx, nm.Message, nm.InboxVers, uid, nm.ConvID, 566 nm.TopicType) { 567 g.Debug(ctx, "chat activity: flip message handled, early out") 568 return nil 569 } 570 571 // Update typing status to stopped 572 g.typingMonitor.Update(ctx, chat1.TyperInfo{ 573 Uid: keybase1.UID(nm.Message.ClientHeader.Sender.String()), 574 DeviceID: keybase1.DeviceID(nm.Message.ClientHeader.SenderDevice.String()), 575 }, convID, chat1.TeamType_NONE, false) 576 577 // Check for a leave message from ourselves and just bail out if it is 578 if nm.Message.GetMessageType() == chat1.MessageType_LEAVE && 579 nm.Message.ClientHeader.Sender.Eq(uid) { 580 g.Debug(ctx, "chat activity: ignoring leave message from oursevles") 581 if err := g.G().InboxSource.UpdateInboxVersion(ctx, uid, nm.InboxVers); err != nil { 582 g.Debug(ctx, "chat activity: failed to update inbox version: %s", err) 583 } 584 return nil 585 } 586 587 decmsg, appended, pushErr := g.G().ConvSource.Push(ctx, nm.ConvID, gregor1.UID(uid), nm.Message) 588 if pushErr != nil { 589 g.Debug(ctx, "chat activity: unable to push message: %v", pushErr) 590 } 591 if conv, err = g.G().InboxSource.NewMessage(ctx, uid, nm.InboxVers, nm.ConvID, 592 nm.Message, nm.MaxMsgs); err != nil { 593 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 594 } 595 // Add on reply information if we have it 596 if pushErr == nil { 597 decmsg, pushErr = g.getReplyMessage(ctx, uid, conv, decmsg) 598 if pushErr != nil { 599 g.Debug(ctx, "chat activity: failed to get reply for push: %s", err) 600 } 601 } 602 603 // If we have no error on this message, then notify the frontend 604 if pushErr == nil { 605 // Make a pagination object so client can use it in GetThreadLocal 606 pmsgs := []pager.Message{nm.Message} 607 pager := pager.NewThreadPager() 608 page, err := pager.MakePage(pmsgs, 1, 0) 609 if err != nil { 610 g.Debug(ctx, "chat activity: error making page: %v", err) 611 } 612 613 desktopNotification := g.shouldDisplayDesktopNotification(ctx, uid, conv, decmsg, nm.UntrustedTeamRole) 614 notificationSnippet := "" 615 if desktopNotification { 616 plaintextDesktopDisabled, err := getPlaintextDesktopDisabled(ctx, g.G()) 617 if err != nil { 618 g.Debug(ctx, "chat activity: unable to get app notification settings: %v defaulting to disable plaintext", err) 619 } 620 notificationSnippet = utils.GetDesktopNotificationSnippet(ctx, g.G(), uid, conv, 621 g.G().Env.GetUsername().String(), &decmsg, plaintextDesktopDisabled) 622 } 623 activity = new(chat1.ChatActivity) 624 *activity = chat1.NewChatActivityWithIncomingMessage(chat1.IncomingMessage{ 625 Message: utils.PresentMessageUnboxed(ctx, g.G(), decmsg, uid, nm.ConvID), 626 ModifiedMessage: g.getSupersedesTarget(ctx, uid, conv, decmsg), 627 ConvID: nm.ConvID, 628 Conv: g.presentUIItem(ctx, conv, uid, 629 utils.PresentParticipantsModeSkip), 630 DisplayDesktopNotification: desktopNotification, 631 DesktopNotificationSnippet: notificationSnippet, 632 Pagination: utils.PresentPagination(page), 633 }) 634 } 635 636 // If this message was not "appended", meaning there is a hole between what we have in cache, 637 // and this message, then we send out a notification that this thread should be considered 638 // stale. 639 // We also get here if we had an error unboxing the messages, it could be a temporal thing 640 // so the frontend should reload. 641 if !appended || pushErr != nil { 642 if !appended { 643 g.Debug(ctx, "chat activity: newMessage: non-append message, alerting") 644 } 645 if pushErr != nil { 646 g.Debug(ctx, "chat activity: newMessage: push error, alerting") 647 } 648 supdate := []chat1.ConversationStaleUpdate{{ 649 ConvID: nm.ConvID, 650 UpdateType: chat1.StaleUpdateType_CLEAR, 651 }} 652 g.G().Syncer.SendChatStaleNotifications(ctx, m.UID().Bytes(), supdate, true) 653 } 654 case types.ActionReadMessage: 655 var nm chat1.ReadMessagePayload 656 if err = dec.Decode(&nm); err != nil { 657 g.Debug(ctx, "chat activity: error decoding: %v", err) 658 return err 659 } 660 g.Debug(ctx, "chat activity: readMessage: convID: %s msgID: %d", nm.ConvID, nm.MsgID) 661 662 uid := m.UID().Bytes() 663 if conv, err = g.G().InboxSource.ReadMessage(ctx, uid, nm.InboxVers, nm.ConvID, nm.MsgID); err != nil { 664 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 665 } 666 activity = new(chat1.ChatActivity) 667 *activity = chat1.NewChatActivityWithReadMessage(chat1.ReadMessageInfo{ 668 MsgID: nm.MsgID, 669 ConvID: nm.ConvID, 670 Conv: g.presentUIItem(ctx, conv, uid, utils.PresentParticipantsModeSkip), 671 }) 672 case types.ActionSetStatus: 673 var nm chat1.SetStatusPayload 674 if err = dec.Decode(&nm); err != nil { 675 g.Debug(ctx, "chat activity: error decoding: %v", err) 676 return err 677 } 678 g.Debug(ctx, "chat activity: setStatus: convID: %s status: %d", nm.ConvID, nm.Status) 679 680 uid := m.UID().Bytes() 681 conv, err = g.G().InboxSource.SetStatus(ctx, uid, nm.InboxVers, nm.ConvID, nm.Status) 682 if err != nil { 683 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 684 } 685 activity = new(chat1.ChatActivity) 686 *activity = chat1.NewChatActivityWithSetStatus(chat1.SetStatusInfo{ 687 ConvID: nm.ConvID, 688 Status: nm.Status, 689 Conv: g.presentUIItem(ctx, conv, uid, utils.PresentParticipantsModeSkip), 690 }) 691 case types.ActionSetAppNotificationSettings: 692 var nm chat1.SetAppNotificationSettingsPayload 693 if err = dec.Decode(&nm); err != nil { 694 g.Debug(ctx, "chat activity: error decoding: %v", err) 695 return err 696 } 697 g.Debug(ctx, "chat activity: setAppNotificationSettings: convID: %s num settings: %d", 698 nm.ConvID, len(nm.Settings.Settings)) 699 700 uid := m.UID().Bytes() 701 if _, err = g.G().InboxSource.SetAppNotificationSettings(ctx, uid, nm.InboxVers, 702 nm.ConvID, nm.Settings); err != nil { 703 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 704 } 705 info := chat1.SetAppNotificationSettingsInfo{ 706 ConvID: nm.ConvID, 707 Settings: nm.Settings, 708 } 709 activity = new(chat1.ChatActivity) 710 *activity = chat1.NewChatActivityWithSetAppNotificationSettings(info) 711 case types.ActionNewConversation: 712 var nm chat1.NewConversationPayload 713 if err = dec.Decode(&nm); err != nil { 714 g.Debug(ctx, "chat activity: error decoding: %v", err) 715 return err 716 } 717 g.Debug(ctx, "chat activity: newConversation: convID: %s ", nm.ConvID) 718 719 uid := m.UID().Bytes() 720 721 // If the topic type is DEV, skip the localization step 722 var inbox types.Inbox 723 switch nm.TopicType { 724 case chat1.TopicType_CHAT, chat1.TopicType_KBFSFILEEDIT, chat1.TopicType_EMOJI, 725 chat1.TopicType_EMOJICROSS: 726 if inbox, _, err = g.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 727 types.InboxSourceDataSourceRemoteOnly, 728 nil, &chat1.GetInboxLocalQuery{ 729 ConvIDs: []chat1.ConversationID{nm.ConvID}, 730 MemberStatus: chat1.AllConversationMemberStatuses(), 731 }); err != nil { 732 g.Debug(ctx, "chat activity: unable to read conversation: %v", err) 733 return err 734 } 735 default: 736 if inbox, err = g.G().InboxSource.ReadUnverified(ctx, uid, 737 types.InboxSourceDataSourceRemoteOnly, 738 &chat1.GetInboxQuery{ 739 ConvIDs: []chat1.ConversationID{nm.ConvID}, 740 MemberStatus: chat1.AllConversationMemberStatuses(), 741 }); err != nil { 742 g.Debug(ctx, "chat activity: unable to read unverified conversation: %v", err) 743 return err 744 } 745 } 746 747 if len(inbox.ConvsUnverified) != 1 { 748 g.Debug(ctx, "chat activity: unable to find conversation, found: %d, expected 1", len(inbox.Convs)) 749 return nil 750 } 751 752 updateConv := inbox.ConvsUnverified[0].Conv 753 if err = g.G().InboxSource.NewConversation(ctx, uid, nm.InboxVers, updateConv); err != nil { 754 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 755 } 756 switch memberStatus := updateConv.ReaderInfo.Status; memberStatus { 757 case chat1.ConversationMemberStatus_LEFT, chat1.ConversationMemberStatus_NEVER_JOINED: 758 g.Debug(ctx, "chat activity: newConversation: suppressing ChatActivity, membersStatus: %v", memberStatus) 759 default: 760 var conv *chat1.ConversationLocal 761 if len(inbox.Convs) == 1 { 762 conv = &inbox.Convs[0] 763 } 764 activity = new(chat1.ChatActivity) 765 *activity = chat1.NewChatActivityWithNewConversation(chat1.NewConversationInfo{ 766 Conv: g.presentUIItem(ctx, conv, uid, utils.PresentParticipantsModeInclude), 767 ConvID: updateConv.GetConvID(), 768 }) 769 } 770 case types.ActionTeamType: 771 var nm chat1.TeamTypePayload 772 if err = dec.Decode(&nm); err != nil { 773 g.Debug(ctx, "chat activity: error decoding: %v", err) 774 return err 775 } 776 g.Debug(ctx, "chat activity: team type: convID: %s ", nm.ConvID) 777 778 uid := m.UID().Bytes() 779 if conv, err = g.G().InboxSource.TeamTypeChanged(ctx, uid, nm.InboxVers, nm.ConvID, nm.TeamType); err != nil { 780 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 781 } 782 activity = new(chat1.ChatActivity) 783 *activity = chat1.NewChatActivityWithTeamtype(chat1.TeamTypeInfo{ 784 ConvID: nm.ConvID, 785 TeamType: nm.TeamType, 786 Conv: g.presentUIItem(ctx, conv, uid, utils.PresentParticipantsModeSkip), 787 }) 788 case types.ActionExpunge: 789 var nm chat1.ExpungePayload 790 if err = dec.Decode(&nm); err != nil { 791 g.Debug(ctx, "chat activity: error decoding: %v", err) 792 return err 793 } 794 g.Debug(ctx, "chat activity: expunge: convID: %s expunge: %v", 795 nm.ConvID, nm.Expunge) 796 uid := m.UID().Bytes() 797 conv, err := utils.GetUnverifiedConv(ctx, g.G(), uid, nm.ConvID, types.InboxSourceDataSourceAll) 798 switch err { 799 case nil: 800 case utils.ErrGetUnverifiedConvNotFound: 801 conv = types.NewEmptyRemoteConversation(convID) 802 default: 803 return err 804 } 805 if err = g.G().ConvSource.Expunge(ctx, conv, uid, nm.Expunge); err != nil { 806 g.Debug(ctx, "chat activity: unable to update conv: %v", err) 807 } 808 if _, err = g.G().InboxSource.Expunge(ctx, uid, nm.InboxVers, nm.ConvID, nm.Expunge, nm.MaxMsgs); err != nil { 809 g.Debug(ctx, "chat activity: unable to update inbox: %v", err) 810 } 811 default: 812 g.Debug(ctx, "unhandled chat.activity action %q", action) 813 return nil 814 } 815 if gm.UnreadUpdate != nil { 816 g.G().Badger.PushChatUpdate(ctx, *gm.UnreadUpdate, gm.InboxVers) 817 } 818 if activity != nil { 819 g.notifyNewChatActivity(ctx, m.UID().(gregor1.UID), gm.TopicType, activity) 820 } else { 821 g.Debug(ctx, "chat activity: skipping notify, activity is nil") 822 } 823 return nil 824 } 825 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 826 return nil 827 } 828 829 func (g *PushHandler) notifyNewChatActivity(ctx context.Context, uid gregor1.UID, 830 topicType chat1.TopicType, activity *chat1.ChatActivity) { 831 g.G().ActivityNotifier.Activity(ctx, uid, topicType, activity, chat1.ChatActivitySource_REMOTE) 832 } 833 834 func (g *PushHandler) notifyConvUpdates(ctx context.Context, uid gregor1.UID, 835 convs []chat1.ConversationLocal) { 836 for _, conv := range convs { 837 g.G().ActivityNotifier.ConvUpdate(ctx, uid, conv.GetConvID(), 838 conv.GetTopicType(), g.presentUIItem(ctx, &conv, uid, utils.PresentParticipantsModeInclude)) 839 } 840 } 841 842 func (g *PushHandler) notifyJoinChannel(ctx context.Context, uid gregor1.UID, 843 conv chat1.ConversationLocal) { 844 g.G().ActivityNotifier.JoinedConversation(ctx, uid, conv.GetConvID(), 845 conv.GetTopicType(), g.presentUIItem(ctx, &conv, uid, utils.PresentParticipantsModeInclude)) 846 } 847 848 func (g *PushHandler) notifyLeftChannel(ctx context.Context, uid gregor1.UID, 849 convID chat1.ConversationID, topicType chat1.TopicType) { 850 g.G().ActivityNotifier.LeftConversation(ctx, uid, convID, topicType) 851 } 852 853 func (g *PushHandler) notifyReset(ctx context.Context, uid gregor1.UID, 854 convID chat1.ConversationID, topicType chat1.TopicType) { 855 g.G().ActivityNotifier.ResetConversation(ctx, uid, convID, topicType) 856 } 857 858 func (g *PushHandler) notifyMembersUpdate(ctx context.Context, uid gregor1.UID, 859 membersRes types.MembershipUpdateRes) { 860 // Build a map of uid -> username for this update 861 var uids []keybase1.UID 862 for _, uid := range membersRes.AllOtherUsers() { 863 uids = append(uids, keybase1.UID(uid.String())) 864 } 865 uidMap := make(map[string]string) 866 packages, err := g.G().UIDMapper.MapUIDsToUsernamePackages(ctx, g.G(), uids, 0, 0, false) 867 if err == nil { 868 for index, p := range packages { 869 uidMap[uids[index].String()] = p.NormalizedUsername.String() 870 } 871 } else { 872 g.Debug(ctx, "notifyMembersUpdate: failed to get usernames, not sending them: %v", err) 873 } 874 convMap := make(map[chat1.ConvIDStr][]chat1.MemberInfo) 875 addStatus := func(status chat1.ConversationMemberStatus, l []chat1.ConversationMember) { 876 for _, cm := range l { 877 if cm.TopicType != chat1.TopicType_CHAT { 878 continue 879 } 880 convIDStr := cm.ConvID.ConvIDStr() 881 if _, ok := convMap[convIDStr]; !ok { 882 convMap[convIDStr] = []chat1.MemberInfo{} 883 } 884 if uname, ok := uidMap[cm.Uid.String()]; ok { 885 convMap[convIDStr] = append(convMap[convIDStr], chat1.MemberInfo{ 886 Member: uname, 887 Status: status, 888 }) 889 } 890 } 891 } 892 addStatus(chat1.ConversationMemberStatus_ACTIVE, membersRes.OthersJoinedConvs) 893 addStatus(chat1.ConversationMemberStatus_RESET, membersRes.OthersResetConvs) 894 addStatus(chat1.ConversationMemberStatus_REMOVED, membersRes.OthersRemovedConvs) 895 for strConvID, memberInfo := range convMap { 896 convID, _ := chat1.MakeConvID(strConvID.String()) 897 activity := chat1.NewChatActivityWithMembersUpdate(chat1.MembersUpdateInfo{ 898 ConvID: convID, 899 Members: memberInfo, 900 }) 901 g.notifyNewChatActivity(ctx, uid, chat1.TopicType_CHAT, &activity) 902 } 903 } 904 905 func (g *PushHandler) notifyConversationsStale(ctx context.Context, uid gregor1.UID, 906 updates []chat1.ConversationUpdate) { 907 var supdate []chat1.ConversationStaleUpdate 908 for _, update := range updates { 909 supdate = append(supdate, chat1.ConversationStaleUpdate{ 910 ConvID: update.ConvID, 911 UpdateType: chat1.StaleUpdateType_CLEAR, 912 }) 913 } 914 g.G().Syncer.SendChatStaleNotifications(ctx, uid, supdate, true) 915 } 916 917 func (g *PushHandler) Typing(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 918 var identBreaks []keybase1.TLFIdentifyFailure 919 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 920 g.identNotifier) 921 // Dont' do a full trace here, keep log spam down 922 defer func() { 923 if err != nil { 924 g.Debug(ctx, "Typing: unable to complete %v", err) 925 } 926 }() 927 if m.Body() == nil { 928 return errors.New("gregor handler for typing: nil message body") 929 } 930 931 var update chat1.RemoteUserTypingUpdate 932 reader := bytes.NewReader(m.Body().Bytes()) 933 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 934 if err = dec.Decode(&update); err != nil { 935 return err 936 } 937 938 // Lookup username and device name 939 kuid := keybase1.UID(update.Uid.String()) 940 kdid := keybase1.DeviceID(update.DeviceID.String()) 941 user, err := g.G().GetUPAKLoader().LookupUsername(ctx, kuid) 942 if err != nil { 943 g.Debug(ctx, "Typing: failed to lookup username/device: msg: %v", err) 944 return err 945 } 946 947 // Fire off update with all relevant info 948 g.typingMonitor.Update(ctx, chat1.TyperInfo{ 949 Uid: kuid, 950 DeviceID: kdid, 951 Username: user.String(), 952 }, update.ConvID, update.TeamType, update.Typing) 953 return nil 954 } 955 956 func (g *PushHandler) UpgradeKBFSToImpteam(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 957 var identBreaks []keybase1.TLFIdentifyFailure 958 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 959 g.identNotifier) 960 defer g.Trace(ctx, &err, "UpgradeKBFSToImpteam")() 961 if m.Body() == nil { 962 return errors.New("gregor handler for upgrade KBFS: nil message body") 963 } 964 965 var update chat1.KBFSImpteamUpgradeUpdate 966 reader := bytes.NewReader(m.Body().Bytes()) 967 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 968 if err = dec.Decode(&update); err != nil { 969 return err 970 } 971 uid := gregor1.UID(m.UID().Bytes()) 972 973 // Order updates based on inbox version of the update from the server 974 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 975 f := func(ctx context.Context) (err error) { 976 defer g.Trace(ctx, &err, "UpgradeKBFSToImpteam(goroutine)")() 977 <-cb 978 g.Lock() 979 defer g.Unlock() 980 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 981 982 if _, err = g.G().InboxSource.UpgradeKBFSToImpteam(ctx, uid, update.InboxVers, 983 update.ConvID); err != nil { 984 g.Debug(ctx, "UpgradeKBFSToImpteam: failed to update KBFS upgrade: %v", err) 985 return err 986 } 987 988 // Just blow away anything we have locally, there might be unboxing errors in here during the 989 // transition. 990 if err = g.G().ConvSource.Clear(ctx, update.ConvID, uid, nil); err != nil { 991 g.Debug(ctx, "UpgradeKBFSToImpteam: failed to clear convsource: %v", err) 992 } 993 g.G().ActivityNotifier.KBFSToImpteamUpgrade(ctx, uid, update.ConvID, update.TopicType) 994 return nil 995 } 996 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 997 998 return nil 999 } 1000 1001 func (g *PushHandler) MembershipUpdate(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1002 var identBreaks []keybase1.TLFIdentifyFailure 1003 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 1004 g.identNotifier) 1005 defer g.Trace(ctx, &err, "MembershipUpdate")() 1006 if m.Body() == nil { 1007 return errors.New("gregor handler for membership update: nil message body") 1008 } 1009 1010 var update chat1.UpdateConversationMembership 1011 reader := bytes.NewReader(m.Body().Bytes()) 1012 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1013 if err = dec.Decode(&update); err != nil { 1014 return err 1015 } 1016 uid := gregor1.UID(m.UID().Bytes()) 1017 1018 // Order updates based on inbox version of the update from the server 1019 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1020 f := func(ctx context.Context) (err error) { 1021 defer g.Trace(ctx, &err, "MembershipUpdate(goroutine)")() 1022 <-cb 1023 g.Lock() 1024 defer g.Unlock() 1025 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1026 1027 // Write out changes to local storage 1028 updateRes, err := g.G().InboxSource.MembershipUpdate(ctx, uid, update.InboxVers, update.Joined, 1029 update.Removed, update.Reset, update.Previewed, update.TeamMemberRoleUpdate) 1030 if err != nil { 1031 g.Debug(ctx, "MembershipUpdate: failed to update membership on inbox: %v", err) 1032 return err 1033 } 1034 1035 // Send out notifications 1036 for _, c := range updateRes.UserJoinedConvs { 1037 g.notifyJoinChannel(ctx, uid, c) 1038 } 1039 for _, c := range updateRes.UserRemovedConvs { 1040 g.notifyLeftChannel(ctx, uid, c.ConvID, c.TopicType) 1041 } 1042 for _, c := range updateRes.UserResetConvs { 1043 g.notifyReset(ctx, uid, c.ConvID, c.TopicType) 1044 } 1045 g.notifyMembersUpdate(ctx, uid, updateRes) 1046 g.notifyConvUpdates(ctx, uid, updateRes.RoleUpdates) 1047 1048 // Fire off badger updates 1049 if update.UnreadUpdate != nil { 1050 g.G().Badger.PushChatUpdate(ctx, *update.UnreadUpdate, update.InboxVers) 1051 } 1052 for _, upd := range update.UnreadUpdates { 1053 g.G().Badger.PushChatUpdate(ctx, upd, update.InboxVers) 1054 } 1055 1056 return nil 1057 } 1058 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1059 1060 return nil 1061 } 1062 1063 func (g *PushHandler) ConversationsUpdate(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1064 var identBreaks []keybase1.TLFIdentifyFailure 1065 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 1066 g.identNotifier) 1067 defer g.Trace(ctx, &err, "ConversationsUpdate")() 1068 if m.Body() == nil { 1069 return errors.New("gregor handler for conversations update: nil message body") 1070 } 1071 1072 var update chat1.UpdateConversations 1073 reader := bytes.NewReader(m.Body().Bytes()) 1074 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1075 if err = dec.Decode(&update); err != nil { 1076 return err 1077 } 1078 uid := gregor1.UID(m.UID().Bytes()) 1079 1080 // Order updates based on inbox version of the update from the server 1081 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1082 f := func(ctx context.Context) (err error) { 1083 defer g.Trace(ctx, &err, "ConversationsUpdate(goroutine)")() 1084 <-cb 1085 g.Lock() 1086 defer g.Unlock() 1087 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1088 1089 // Write out changes to local storage 1090 if err := g.G().InboxSource.ConversationsUpdate(ctx, uid, update.InboxVers, update.ConvUpdates); err != nil { 1091 g.Debug(ctx, "ConversationsUpdate: failed to update membership on inbox: %v", err) 1092 return err 1093 } 1094 1095 // Send out notifications 1096 g.notifyConversationsStale(ctx, uid, update.ConvUpdates) 1097 return nil 1098 } 1099 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1100 1101 return nil 1102 } 1103 1104 func (g *PushHandler) SetConvRetention(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1105 var identBreaks []keybase1.TLFIdentifyFailure 1106 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 1107 g.identNotifier) 1108 defer g.Trace(ctx, &err, "SetConvRetention")() 1109 if m.Body() == nil { 1110 return errors.New("gregor handler for SetConvRetention update: nil message body") 1111 } 1112 1113 var update chat1.SetConvRetentionUpdate 1114 reader := bytes.NewReader(m.Body().Bytes()) 1115 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1116 if err = dec.Decode(&update); err != nil { 1117 return err 1118 } 1119 uid := gregor1.UID(m.UID().Bytes()) 1120 1121 // Order updates based on inbox version of the update from the server 1122 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1123 f := func(ctx context.Context) (err error) { 1124 defer g.Trace(ctx, &err, "SetConvRetention(goroutine)")() 1125 <-cb 1126 g.Lock() 1127 defer g.Unlock() 1128 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1129 1130 // Update inbox 1131 conv, err := g.G().InboxSource.SetConvRetention(ctx, m.UID().Bytes(), update.InboxVers, 1132 update.ConvID, update.Policy) 1133 if err != nil { 1134 g.Debug(ctx, "SetConvRetention: unable to update inbox: %v", err) 1135 return err 1136 } 1137 if conv == nil { 1138 return nil 1139 } 1140 // Send notify for the conv 1141 g.G().ActivityNotifier.SetConvRetention(ctx, uid, 1142 conv.GetConvID(), conv.GetTopicType(), g.presentUIItem(ctx, conv, uid, 1143 utils.PresentParticipantsModeSkip)) 1144 return nil 1145 } 1146 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1147 1148 return nil 1149 } 1150 1151 func (g *PushHandler) SetTeamRetention(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1152 var identBreaks []keybase1.TLFIdentifyFailure 1153 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 1154 g.identNotifier) 1155 defer g.Trace(ctx, &err, "SetTeamRetention")() 1156 if m.Body() == nil { 1157 return errors.New("gregor handler for SetTeamRetention update: nil message body") 1158 } 1159 1160 var update chat1.SetTeamRetentionUpdate 1161 reader := bytes.NewReader(m.Body().Bytes()) 1162 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1163 if err = dec.Decode(&update); err != nil { 1164 return err 1165 } 1166 uid := gregor1.UID(m.UID().Bytes()) 1167 1168 // Order updates based on inbox version of the update from the server 1169 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1170 f := func(ctx context.Context) (err error) { 1171 defer g.Trace(ctx, &err, "SetTeamRetention(goroutine)")() 1172 <-cb 1173 g.Lock() 1174 defer g.Unlock() 1175 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1176 1177 // Update inbox 1178 var convs []chat1.ConversationLocal 1179 if convs, err = g.G().InboxSource.SetTeamRetention(ctx, m.UID().Bytes(), update.InboxVers, 1180 update.TeamID, update.Policy); err != nil { 1181 g.Debug(ctx, "SetTeamRetention: unable to update inbox: %v", err) 1182 return err 1183 } 1184 if len(convs) == 0 { 1185 g.Debug(ctx, "SetTeamRetention: no local convs affected") 1186 return nil 1187 } 1188 // Send notify for each conversation ID 1189 convUIItems := make(map[chat1.TopicType][]chat1.InboxUIItem) 1190 for _, conv := range convs { 1191 uiItem := g.presentUIItem(ctx, &conv, uid, utils.PresentParticipantsModeSkip) 1192 if uiItem != nil { 1193 convUIItems[uiItem.TopicType] = append(convUIItems[uiItem.TopicType], *uiItem) 1194 } 1195 } 1196 for topicType, items := range convUIItems { 1197 g.G().ActivityNotifier.SetTeamRetention(ctx, uid, update.TeamID, topicType, items) 1198 } 1199 return nil 1200 } 1201 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1202 1203 return nil 1204 } 1205 1206 func (g *PushHandler) SetConvSettings(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1207 var identBreaks []keybase1.TLFIdentifyFailure 1208 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 1209 g.identNotifier) 1210 defer g.Trace(ctx, &err, "SetConvSettings")() 1211 if m.Body() == nil { 1212 return errors.New("gregor handler for SetConvSettings update: nil message body") 1213 } 1214 1215 var update chat1.SetConvSettingsUpdate 1216 reader := bytes.NewReader(m.Body().Bytes()) 1217 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1218 if err = dec.Decode(&update); err != nil { 1219 return err 1220 } 1221 uid := gregor1.UID(m.UID().Bytes()) 1222 1223 // Order updates based on inbox version of the update from the server 1224 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1225 f := func(ctx context.Context) (err error) { 1226 defer g.Trace(ctx, &err, "SetConvSettings(goroutine)")() 1227 <-cb 1228 g.Lock() 1229 defer g.Unlock() 1230 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1231 1232 // Update inbox 1233 conv, err := g.G().InboxSource.SetConvSettings(ctx, m.UID().Bytes(), update.InboxVers, 1234 update.ConvID, update.ConvSettings) 1235 if err != nil { 1236 g.Debug(ctx, "SetConvSettings: unable to update inbox: %v", err) 1237 return err 1238 } 1239 if conv == nil { 1240 return nil 1241 } 1242 // Send notify for the conv 1243 g.G().ActivityNotifier.SetConvSettings(ctx, uid, 1244 conv.GetConvID(), conv.GetTopicType(), g.presentUIItem(ctx, conv, uid, 1245 utils.PresentParticipantsModeSkip)) 1246 return nil 1247 } 1248 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1249 1250 return nil 1251 } 1252 1253 func (g *PushHandler) SubteamRename(ctx context.Context, m gregor.OutOfBandMessage) (err error) { 1254 defer g.Trace(ctx, &err, "SubteamRename")() 1255 if m.Body() == nil { 1256 return errors.New("gregor handler for chat.subteamRename: nil message body") 1257 } 1258 1259 var update chat1.SubteamRenameUpdate 1260 reader := bytes.NewReader(m.Body().Bytes()) 1261 dec := codec.NewDecoder(reader, &codec.MsgpackHandle{WriteExt: true}) 1262 if err = dec.Decode(&update); err != nil { 1263 return err 1264 } 1265 uid := gregor1.UID(m.UID().Bytes()) 1266 1267 // Order updates based on inbox version of the update from the server 1268 cb := g.orderer.WaitForTurn(ctx, uid, update.InboxVers) 1269 f := func(ctx context.Context) (err error) { 1270 defer g.Trace(ctx, &err, "SubteamRename(goroutine)")() 1271 <-cb 1272 g.Lock() 1273 defer g.Unlock() 1274 defer g.orderer.CompleteTurn(ctx, uid, update.InboxVers) 1275 // Update inbox and get conversations 1276 ib, _, err := g.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 1277 types.InboxSourceDataSourceAll, nil, &chat1.GetInboxLocalQuery{ 1278 ConvIDs: update.ConvIDs, 1279 }) 1280 if err != nil { 1281 g.Debug(ctx, "SubteamRename: unable to read conversation: %v", err) 1282 return err 1283 } 1284 convs := ib.Convs 1285 if len(convs) != len(update.ConvIDs) { 1286 g.Debug(ctx, "SubteamRename: unable to find all conversations") 1287 } 1288 1289 convUIItems := make(map[chat1.TopicType][]chat1.InboxUIItem) 1290 convIDs := make(map[chat1.TopicType][]chat1.ConversationID) 1291 tlfIDs := make(map[chat1.TLFIDStr]struct{}) 1292 for _, conv := range convs { 1293 tlfIDs[conv.Info.Triple.Tlfid.TLFIDStr()] = struct{}{} 1294 uiItem := g.presentUIItem(ctx, &conv, uid, utils.PresentParticipantsModeSkip) 1295 if uiItem != nil { 1296 convUIItems[uiItem.TopicType] = append(convUIItems[uiItem.TopicType], *uiItem) 1297 convIDs[uiItem.TopicType] = append(convIDs[uiItem.TopicType], conv.GetConvID()) 1298 } 1299 } 1300 1301 // force refresh any affected teams 1302 m := libkb.NewMetaContext(ctx, g.G().ExternalG()) 1303 for tlfID := range tlfIDs { 1304 teamID, err := keybase1.TeamIDFromString(tlfID.String()) 1305 if err != nil { 1306 g.Debug(ctx, "SubteamRename: unable to get teamID: %v", err) 1307 continue 1308 } 1309 1310 _, err = m.G().GetFastTeamLoader().Load(m, keybase1.FastTeamLoadArg{ 1311 ID: teamID, 1312 Public: teamID.IsPublic(), 1313 ForceRefresh: true, 1314 }) 1315 if err != nil { 1316 g.Debug(ctx, "SubteamRename: unable to force-refresh team: %v", err) 1317 continue 1318 } 1319 } 1320 if _, err := g.G().InboxSource.SubteamRename(ctx, uid, update.InboxVers, update.ConvIDs); err != nil { 1321 g.Debug(ctx, "SubteamRename: failed to process on inbox: %s", err) 1322 } 1323 for topicType, items := range convUIItems { 1324 cids := convIDs[topicType] 1325 g.G().ActivityNotifier.SubteamRename(ctx, uid, cids, topicType, items) 1326 } 1327 return nil 1328 } 1329 g.eg.Go(func() error { return f(globals.BackgroundChatCtx(ctx, g.G())) }) 1330 1331 return nil 1332 } 1333 1334 func (g *PushHandler) HandleOobm(ctx context.Context, obm gregor.OutOfBandMessage) (bool, error) { 1335 // Don't process messages if we have not started. 1336 g.startMu.Lock() 1337 defer g.startMu.Unlock() 1338 if !g.started { 1339 return false, nil 1340 } 1341 1342 if g.testingIgnoreBroadcasts { 1343 return false, errors.New("ignoring broadcasts for tests") 1344 } 1345 if obm.System() == nil { 1346 return false, errors.New("nil system in out of band message") 1347 } 1348 1349 switch obm.System().String() { 1350 case types.PushActivity: 1351 return true, g.Activity(ctx, obm) 1352 case types.PushTLFFinalize: 1353 return true, g.TlfFinalize(ctx, obm) 1354 case types.PushTLFResolve: 1355 return true, g.TlfResolve(ctx, obm) 1356 case types.PushTyping: 1357 return true, g.Typing(ctx, obm) 1358 case types.PushMembershipUpdate: 1359 return true, g.MembershipUpdate(ctx, obm) 1360 case types.PushConvRetention: 1361 return true, g.SetConvRetention(ctx, obm) 1362 case types.PushTeamRetention: 1363 return true, g.SetTeamRetention(ctx, obm) 1364 case types.PushConvSettings: 1365 return true, g.SetConvSettings(ctx, obm) 1366 case types.PushKBFSUpgrade: 1367 return true, g.UpgradeKBFSToImpteam(ctx, obm) 1368 case types.PushSubteamRename: 1369 return true, g.SubteamRename(ctx, obm) 1370 case types.PushConversationsUpdate: 1371 return true, g.ConversationsUpdate(ctx, obm) 1372 } 1373 1374 return false, nil 1375 }