github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/chat_rpc.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "context" 9 "sync" 10 "time" 11 12 "github.com/keybase/client/go/kbfs/favorites" 13 "github.com/keybase/client/go/kbfs/kbfsedits" 14 "github.com/keybase/client/go/kbfs/tlf" 15 "github.com/keybase/client/go/kbfs/tlfhandle" 16 "github.com/keybase/client/go/libkb" 17 "github.com/keybase/client/go/logger" 18 "github.com/keybase/client/go/protocol/chat1" 19 "github.com/keybase/client/go/protocol/keybase1" 20 "github.com/keybase/go-framed-msgpack-rpc/rpc" 21 "github.com/pkg/errors" 22 ) 23 24 const ( 25 // The name of the channel in the logged-in user's private 26 // self-conversation (of type kbfs-edits) that stores a history of 27 // which TLFs the user has written to. 28 selfWriteChannel = "_self" 29 30 // The topic type of the self-write channel. 31 selfWriteType = chat1.TopicType_KBFSFILEEDIT 32 33 // numSelfTlfs is the number of self-written TLFs to include in 34 // the results of GetGroupedInbox. 35 numSelfTlfs = 3 36 ) 37 38 // ChatRPC is an RPC based implementation for chat. 39 type ChatRPC struct { 40 config Config 41 log logger.Logger 42 vlog *libkb.VDebugLog 43 deferLog logger.Logger 44 client chat1.LocalInterface 45 46 convLock sync.RWMutex 47 convCBs map[chat1.ConvIDStr][]ChatChannelNewMessageCB 48 selfConvID chat1.ConversationID 49 lastWrittenConvID chat1.ConversationID 50 } 51 52 var _ rpc.ConnectionHandler = (*ChatRPC)(nil) 53 54 // NewChatRPC constructs a new RPC based chat implementation. 55 func NewChatRPC(config Config, kbCtx Context) *ChatRPC { 56 log := config.MakeLogger("") 57 deferLog := log.CloneWithAddedDepth(1) 58 c := &ChatRPC{ 59 log: log, 60 vlog: config.MakeVLogger(log), 61 deferLog: deferLog, 62 config: config, 63 convCBs: make(map[chat1.ConvIDStr][]ChatChannelNewMessageCB), 64 } 65 conn := NewSharedKeybaseConnection(kbCtx, config, c) 66 c.client = chat1.LocalClient{Cli: conn.GetClient()} 67 return c 68 } 69 70 // HandlerName implements the ConnectionHandler interface. 71 func (c *ChatRPC) HandlerName() string { 72 return "Chat" 73 } 74 75 // OnConnect implements the ConnectionHandler interface. 76 func (c *ChatRPC) OnConnect(ctx context.Context, conn *rpc.Connection, 77 _ rpc.GenericClient, server *rpc.Server) error { 78 if c.config.KBFSOps() != nil { 79 c.config.KBFSOps().PushConnectionStatusChange(KeybaseServiceName, nil) 80 } 81 82 err := server.Register(chat1.NotifyChatProtocol(c)) 83 switch err.(type) { 84 case nil, rpc.AlreadyRegisteredError: 85 default: 86 return err 87 } 88 89 return nil 90 } 91 92 // OnConnectError implements the ConnectionHandler interface. 93 func (c *ChatRPC) OnConnectError(err error, wait time.Duration) { 94 c.log.Warning("Chat: connection error: %q; retrying in %s", 95 err, wait) 96 if c.config.KBFSOps() != nil { 97 c.config.KBFSOps().PushConnectionStatusChange(KeybaseServiceName, err) 98 } 99 } 100 101 // OnDoCommandError implements the ConnectionHandler interface. 102 func (c *ChatRPC) OnDoCommandError(err error, wait time.Duration) { 103 c.log.Warning("Chat: docommand error: %q; retrying in %s", 104 err, wait) 105 if c.config.KBFSOps() != nil { 106 c.config.KBFSOps().PushConnectionStatusChange(KeybaseServiceName, err) 107 } 108 } 109 110 // OnDisconnected implements the ConnectionHandler interface. 111 func (c *ChatRPC) OnDisconnected(_ context.Context, 112 status rpc.DisconnectStatus) { 113 if status == rpc.StartingNonFirstConnection { 114 c.log.Warning("Chat is disconnected") 115 if c.config.KBFSOps() != nil { 116 c.config.KBFSOps().PushConnectionStatusChange( 117 KeybaseServiceName, errDisconnected{}) 118 } 119 } 120 } 121 122 // ShouldRetry implements the ConnectionHandler interface. 123 func (c *ChatRPC) ShouldRetry(_ string, _ error) bool { 124 return false 125 } 126 127 // ShouldRetryOnConnect implements the ConnectionHandler interface. 128 func (c *ChatRPC) ShouldRetryOnConnect(err error) bool { 129 _, inputCanceled := err.(libkb.InputCanceledError) 130 return !inputCanceled 131 } 132 133 var _ Chat = (*ChatRPC)(nil) 134 135 // Chat notes (will remove/rework these once the implementation is complete): 136 // 137 // When sending: 138 // * chat1.NewConversationLocal 139 // * chat1.PostLocalNonblock 140 // * ClientPrev can be 0. Can outbox ID be nil? mikem: yes 141 142 // Gathering recent notifications: 143 // * chat1.GetInboxAndUnboxLocal (pagination not needed) 144 // * But we'd need inbox grouping to get this exactly right. 145 146 // Reading each conversation: 147 // * Get list of all channels/writers in the conversation 148 // * chat1.GetTLFConversationsLocal can give us the list of channels, 149 // which corresponds to the set of users who have actually written. 150 // (There might be a lot of team members who haven't written, so 151 // probably best to avoid iterating through everyone.). 152 // * Always look up your own channel though, so the GUI can show your 153 // own last edits if desired. 154 // * Read each channel until getting N updates for each writer 155 // * chat1.GetThreadLocal with pagination 156 // * No prev filled in on next pagination to go backward 157 // * How to reconcile renames, etc across channels? 158 // * It's hard to know if a long-ago writer updated a file, and 159 // later it was renamed by a prolific writer who made N more updates 160 // afterward. 161 // * For performance reasons, we probably just need to show the old 162 // update under the old file name. Should be rare enough that 163 // it doesn't matter. 164 165 // Getting real-time updates: 166 // * New kbfs-edits activity push notifications on notify-router 167 // * Mike will make ticket to auto-join non-chat channels, 168 // so they show up in `GetInboxAndUnboxLocal`. 169 // * Spot-edit the local edit history on each new push notification. 170 171 // One layer over the service RPC connection that takes all needed 172 // arguments (including topic type, etc), and passes it pretty 173 // directly to the RPC. 174 175 // Another, per-TLF layer to remember the resolved conversation ID and 176 // send/read kbfs-edits messages. It should also interface with the 177 // local journal to show the unflushed journal data as part of the 178 // updates. 179 180 // Finally an inbox layer that can read the server inbox, and also 181 // checks the journal status, to return the top set of conversations 182 // at any given time. Maybe it also subscribes to inbox notifications 183 // of some kind. 184 185 func membersTypeFromTlfType(tlfType tlf.Type) chat1.ConversationMembersType { 186 if tlfType == tlf.SingleTeam { 187 return chat1.ConversationMembersType_TEAM 188 } 189 return chat1.ConversationMembersType_IMPTEAMNATIVE 190 } 191 192 // GetConversationID implements the Chat interface. 193 func (c *ChatRPC) GetConversationID( 194 ctx context.Context, tlfName tlf.CanonicalName, tlfType tlf.Type, 195 channelName string, chatType chat1.TopicType) ( 196 chat1.ConversationID, error) { 197 vis := keybase1.TLFVisibility_PRIVATE 198 if tlfType == tlf.Public { 199 vis = keybase1.TLFVisibility_PUBLIC 200 } 201 202 arg := chat1.NewConversationLocalArg{ 203 TlfName: string(tlfName), 204 TopicType: chatType, 205 TopicName: &channelName, 206 TlfVisibility: vis, 207 MembersType: membersTypeFromTlfType(tlfType), 208 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 209 } 210 211 // Try creating the conversation to get back the ID -- if the 212 // conversation already exists, this just returns the existing 213 // conversation. 214 res, err := c.client.NewConversationLocal(ctx, arg) 215 if err != nil { 216 return nil, err 217 } 218 219 return res.Conv.Info.Id, nil 220 } 221 222 func (c *ChatRPC) getSelfConvInfoIfCached() ( 223 selfConvID, lastWrittenConvID chat1.ConversationID) { 224 c.convLock.RLock() 225 defer c.convLock.RUnlock() 226 return c.selfConvID, c.lastWrittenConvID 227 } 228 229 func (c *ChatRPC) getSelfConvInfo(ctx context.Context) ( 230 selfConvID, lastWrittenConvID chat1.ConversationID, err error) { 231 selfConvID, lastWrittenConvID = c.getSelfConvInfoIfCached() 232 if selfConvID != nil { 233 return selfConvID, lastWrittenConvID, err 234 } 235 236 // Otherwise we need to look it up. 237 session, err := c.config.KBPKI().GetCurrentSession(ctx) 238 if err != nil { 239 return nil, nil, err 240 } 241 242 selfConvID, err = c.GetConversationID( 243 ctx, tlf.CanonicalName(session.Name), tlf.Private, selfWriteChannel, 244 selfWriteType) 245 if err != nil { 246 return nil, nil, err 247 } 248 249 messages, _, err := c.ReadChannel(ctx, selfConvID, nil) 250 if err != nil { 251 return nil, nil, err 252 } 253 254 if len(messages) > 0 { 255 selfMessage, err := kbfsedits.ReadSelfWrite(messages[0]) 256 if err != nil { 257 c.log.CDebugf(ctx, "Couldn't read the last self-write message: %+v") 258 } else { 259 lastWrittenConvID = selfMessage.ConvID 260 } 261 } 262 263 c.convLock.Lock() 264 defer c.convLock.Unlock() 265 c.selfConvID = selfConvID 266 c.lastWrittenConvID = lastWrittenConvID 267 return selfConvID, lastWrittenConvID, nil 268 } 269 270 // SendTextMessage implements the Chat interface. 271 func (c *ChatRPC) SendTextMessage( 272 ctx context.Context, tlfName tlf.CanonicalName, tlfType tlf.Type, 273 convID chat1.ConversationID, body string) error { 274 if len(body) == 0 { 275 c.log.CDebugf(ctx, "Ignoring empty message") 276 return nil 277 } 278 279 arg := chat1.PostLocalNonblockArg{ 280 ConversationID: convID, 281 Msg: chat1.MessagePlaintext{ 282 ClientHeader: chat1.MessageClientHeader{ 283 Conv: chat1.ConversationIDTriple{ 284 TopicType: chat1.TopicType_KBFSFILEEDIT, 285 }, 286 TlfName: string(tlfName), 287 TlfPublic: tlfType == tlf.Public, 288 MessageType: chat1.MessageType_TEXT, 289 }, 290 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 291 Body: body, 292 }), 293 }, 294 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 295 } 296 _, err := c.client.PostLocalNonblock(ctx, arg) 297 if err != nil { 298 return err 299 } 300 301 selfConvID, lastWrittenConvID, err := c.getSelfConvInfo(ctx) 302 if err != nil { 303 return err 304 } 305 if lastWrittenConvID.Eq(convID) { 306 // Can skip writing this, since the latest one is the same 307 // conversation. Note that this is slightly racy since 308 // another write can happen in the meantime, but this list 309 // doesn't need to be exact, so best effort is ok. 310 return nil 311 } 312 313 c.vlog.CLogf( 314 ctx, libkb.VLog1, "Writing self-write message to %s", selfConvID) 315 316 session, err := c.config.KBPKI().GetCurrentSession(ctx) 317 if err != nil { 318 return err 319 } 320 321 serverTime := c.config.Clock().Now() 322 if offset, ok := c.config.MDServer().OffsetFromServerTime(); ok { 323 serverTime = serverTime.Add(-offset) 324 } 325 326 selfWriteBody, err := kbfsedits.PrepareSelfWrite(kbfsedits.SelfWriteMessage{ 327 Version: kbfsedits.NotificationV2, 328 Folder: keybase1.Folder{ 329 Name: string(tlfName), 330 FolderType: tlfType.FolderType(), 331 Private: tlfType != tlf.Public, 332 }, 333 ConvID: convID, 334 ServerTime: serverTime, 335 }) 336 if err != nil { 337 return err 338 } 339 340 arg = chat1.PostLocalNonblockArg{ 341 ConversationID: selfConvID, 342 Msg: chat1.MessagePlaintext{ 343 ClientHeader: chat1.MessageClientHeader{ 344 Conv: chat1.ConversationIDTriple{ 345 TopicType: chat1.TopicType_KBFSFILEEDIT, 346 }, 347 TlfName: string(session.Name), 348 TlfPublic: false, 349 MessageType: chat1.MessageType_TEXT, 350 }, 351 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 352 Body: selfWriteBody, 353 }), 354 }, 355 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 356 } 357 _, err = c.client.PostLocalNonblock(ctx, arg) 358 if err != nil { 359 return err 360 } 361 362 c.convLock.Lock() 363 defer c.convLock.Unlock() 364 c.lastWrittenConvID = convID 365 366 return err 367 } 368 369 func (c *ChatRPC) getLastSelfWrittenHandles( 370 ctx context.Context, chatType chat1.TopicType, seen map[string]bool) ( 371 results []*tlfhandle.Handle, err error) { 372 selfConvID, _, err := c.getSelfConvInfo(ctx) 373 if err != nil { 374 return nil, err 375 } 376 var startPage []byte 377 // Search backward until we find numSelfTlfs unique handles. 378 for len(results) < numSelfTlfs { 379 messages, nextPage, err := c.ReadChannel(ctx, selfConvID, startPage) 380 if err != nil { 381 return nil, err 382 } 383 for i := 0; i < len(messages) && len(results) < numSelfTlfs; i++ { 384 selfMessage, err := kbfsedits.ReadSelfWrite(messages[i]) 385 if err != nil { 386 return nil, err 387 } 388 389 tlfName := selfMessage.Folder.Name 390 tlfType := tlf.TypeFromFolderType(selfMessage.Folder.FolderType) 391 392 // Before doing the work of creating a full TLF handle, do 393 // a quick check to see if we've parsed it already (since 394 // the same conversation can show up multiple times in the 395 // self-write list if they are interspersed with writes to 396 // other conversations). Most of the time `tlfName` 397 // should already be canonicalized, so this shouldn't 398 // result in too many false negatives (and won't result in 399 // any false positives). 400 quickPath := tlfhandle.BuildCanonicalPathForTlfName( 401 tlfType, tlf.CanonicalName(tlfName)) 402 if seen[quickPath] { 403 continue 404 } 405 406 h, err := GetHandleFromFolderNameAndType( 407 ctx, c.config.KBPKI(), c.config.MDOps(), c.config, 408 tlfName, tlfType) 409 if err != nil { 410 c.log.CDebugf(ctx, 411 "Ignoring errors getting handle for %s/%s: %+v", 412 tlfName, tlfType, err) 413 continue 414 } 415 416 p := h.GetCanonicalPath() 417 if seen[p] { 418 continue 419 } 420 seen[p] = true 421 results = append(results, h) 422 } 423 424 if nextPage == nil { 425 break 426 } 427 startPage = nextPage 428 } 429 return results, nil 430 } 431 432 // GetGroupedInbox implements the Chat interface. 433 func (c *ChatRPC) GetGroupedInbox( 434 ctx context.Context, chatType chat1.TopicType, maxChats int) ( 435 results []*tlfhandle.Handle, err error) { 436 // First get the latest TLFs written by this user. 437 seen := make(map[string]bool) 438 results, err = c.getLastSelfWrittenHandles(ctx, chatType, seen) 439 if err != nil { 440 return nil, err 441 } 442 443 arg := chat1.GetInboxAndUnboxLocalArg{ 444 Query: &chat1.GetInboxLocalQuery{ 445 TopicType: &chatType, 446 }, 447 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 448 } 449 res, err := c.client.GetInboxAndUnboxLocal(ctx, arg) 450 if err != nil { 451 return nil, err 452 } 453 454 c.config.GetPerfLog().CDebugf( 455 ctx, "GetFavorites GetGroupedInbox") 456 favs, err := c.config.KBFSOps().GetFavorites(ctx) 457 if err != nil { 458 c.log.CWarningf(ctx, 459 "Unable to fetch favorites while making GroupedInbox: %v", 460 err) 461 } 462 favMap := make(map[favorites.Folder]bool) 463 for _, fav := range favs { 464 favMap[fav] = true 465 } 466 467 // Return the first unique `maxChats` chats. Eventually the 468 // service will support grouping these by TLF ID and we won't 469 // have to check for uniques. For now, we might falsely return 470 // fewer than `maxChats` TLFs. TODO: make sure these are ordered 471 // with the most recent one at index 0. 472 for i := 0; i < len(res.Conversations) && len(results) < maxChats; i++ { 473 info := res.Conversations[i].Info 474 if info.TopicName == selfWriteChannel { 475 continue 476 } 477 478 tlfType := tlf.Private 479 if info.Visibility == keybase1.TLFVisibility_PUBLIC { 480 tlfType = tlf.Public 481 } else if info.MembersType == chat1.ConversationMembersType_TEAM { 482 tlfType = tlf.SingleTeam 483 } 484 485 tlfIsFavorite := favMap[favorites.Folder{Name: info.TlfName, Type: tlfType}] 486 if !tlfIsFavorite { 487 continue 488 } 489 490 // Before doing the work of creating a full TLF handle, do a 491 // quick check to see if we've parsed it already (since 492 // multiple conversations can belong to the same TLF due to 493 // multiple writers in that TLF). Most of the time 494 // `info.TlfName` should already be canonicalized, so this 495 // shouldn't result in too many false negatives (and won't 496 // result in any false positives). 497 quickPath := tlfhandle.BuildCanonicalPathForTlfName( 498 tlfType, tlf.CanonicalName(info.TlfName)) 499 if seen[quickPath] { 500 continue 501 } 502 503 h, err := GetHandleFromFolderNameAndType( 504 ctx, c.config.KBPKI(), c.config.MDOps(), c.config, 505 info.TlfName, tlfType) 506 if err != nil { 507 c.log.CDebugf(ctx, "Ignoring errors getting handle for %s/%s: %+v", 508 info.TlfName, tlfType, err) 509 continue 510 } 511 512 p := h.GetCanonicalPath() 513 if seen[p] { 514 continue 515 } 516 seen[p] = true 517 results = append(results, h) 518 } 519 520 return results, nil 521 } 522 523 // GetChannels implements the Chat interface. 524 func (c *ChatRPC) GetChannels( 525 ctx context.Context, tlfName tlf.CanonicalName, tlfType tlf.Type, 526 chatType chat1.TopicType) ( 527 convIDs []chat1.ConversationID, channelNames []string, err error) { 528 expectedVisibility := keybase1.TLFVisibility_PRIVATE 529 if tlfType == tlf.Public { 530 expectedVisibility = keybase1.TLFVisibility_PUBLIC 531 } 532 533 strTlfName := string(tlfName) 534 arg := chat1.GetInboxAndUnboxLocalArg{ 535 Query: &chat1.GetInboxLocalQuery{ 536 Name: &chat1.NameQuery{ 537 Name: strTlfName, 538 MembersType: membersTypeFromTlfType(tlfType), 539 }, 540 TopicType: &chatType, 541 TlfVisibility: &expectedVisibility, 542 }, 543 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 544 } 545 res, err := c.client.GetInboxAndUnboxLocal(ctx, arg) 546 if err != nil { 547 return nil, nil, err 548 } 549 550 for _, conv := range res.Conversations { 551 if conv.Info.Visibility != expectedVisibility { 552 // Skip any conversation that doesn't match our visibility. 553 continue 554 } 555 556 if conv.Info.TopicName == selfWriteChannel { 557 continue 558 } 559 560 convIDs = append(convIDs, conv.Info.Id) 561 channelNames = append(channelNames, conv.Info.TopicName) 562 } 563 564 return convIDs, channelNames, nil 565 } 566 567 const readChannelPageSize = 100 568 569 // ReadChannel implements the Chat interface. 570 func (c *ChatRPC) ReadChannel( 571 ctx context.Context, convID chat1.ConversationID, startPage []byte) ( 572 messages []string, nextPage []byte, err error) { 573 pagination := &chat1.Pagination{Num: readChannelPageSize} 574 if startPage != nil { 575 pagination.Next = startPage 576 } 577 arg := chat1.GetThreadLocalArg{ 578 ConversationID: convID, 579 Pagination: pagination, 580 Reason: chat1.GetThreadReason_KBFSFILEACTIVITY, 581 IdentifyBehavior: keybase1.TLFIdentifyBehavior_KBFS_CHAT, 582 } 583 res, err := c.client.GetThreadLocal(ctx, arg) 584 if err != nil { 585 return nil, nil, err 586 } 587 for _, msg := range res.Thread.Messages { 588 state, err := msg.State() 589 if err != nil { 590 return nil, nil, err 591 } 592 switch state { 593 case chat1.MessageUnboxedState_VALID: 594 msgBody := msg.Valid().MessageBody 595 msgType, err := msgBody.MessageType() 596 if err != nil { 597 return nil, nil, err 598 } 599 if msgType != chat1.MessageType_TEXT { 600 c.vlog.CLogf( 601 ctx, libkb.VLog1, "Ignoring unexpected msg type: %d", 602 msgType) 603 continue 604 } 605 messages = append(messages, msgBody.Text().Body) 606 case chat1.MessageUnboxedState_ERROR: 607 // TODO: Are there any errors we need to tolerate? 608 return nil, nil, errors.New(msg.Error().ErrMsg) 609 default: 610 c.log.CDebugf(ctx, "Ignoring unexpected msg state: %d", state) 611 continue 612 } 613 614 } 615 if res.Thread.Pagination != nil && !res.Thread.Pagination.Last { 616 nextPage = res.Thread.Pagination.Next 617 } 618 return messages, nextPage, nil 619 } 620 621 // RegisterForMessages implements the Chat interface. 622 func (c *ChatRPC) RegisterForMessages( 623 convID chat1.ConversationID, cb ChatChannelNewMessageCB) { 624 str := convID.ConvIDStr() 625 c.convLock.Lock() 626 defer c.convLock.Unlock() 627 c.convCBs[str] = append(c.convCBs[str], cb) 628 } 629 630 // ClearCache implements the Chat interface. 631 func (c *ChatRPC) ClearCache() { 632 c.convLock.Lock() 633 defer c.convLock.Unlock() 634 c.selfConvID = nil 635 c.lastWrittenConvID = nil 636 } 637 638 // We only register for the kbfs-edits type of notification in 639 // keybase_daemon_rpc, so all the other methods below besides 640 // `NewChatActivity` should never be called. 641 var _ chat1.NotifyChatInterface = (*ChatRPC)(nil) 642 643 func (c *ChatRPC) newNotificationChannel( 644 ctx context.Context, convID chat1.ConversationID, 645 conv *chat1.InboxUIItem) error { 646 if conv == nil { 647 c.log.CDebugf(ctx, 648 "No conv for new notification channel %s; ignoring", convID) 649 return nil 650 } 651 tlfType := tlf.Private 652 if conv.Visibility == keybase1.TLFVisibility_PUBLIC { 653 tlfType = tlf.Public 654 } else if conv.MembersType == chat1.ConversationMembersType_TEAM { 655 tlfType = tlf.SingleTeam 656 } 657 658 c.config.GetPerfLog().CDebugf( 659 ctx, "GetFavorites newNotificationChannel") 660 favorites, err := c.config.KBFSOps().GetFavorites(ctx) 661 if err != nil { 662 c.log.CWarningf(ctx, 663 "Unable to fetch favorites while making edit notifications: %v", 664 err) 665 } 666 tlfIsFavorite := false 667 for _, fav := range favorites { 668 if fav.Name == conv.Name && fav.Type == tlfType { 669 tlfIsFavorite = true 670 break 671 } 672 } 673 if !tlfIsFavorite { 674 return nil 675 } 676 677 tlfHandle, err := GetHandleFromFolderNameAndType( 678 ctx, c.config.KBPKI(), c.config.MDOps(), c.config, conv.Name, tlfType) 679 if err != nil { 680 return err 681 } 682 if c.config.KBFSOps() != nil { 683 c.config.KBFSOps().NewNotificationChannel( 684 ctx, tlfHandle, convID, conv.Channel) 685 } 686 return nil 687 } 688 689 func (c *ChatRPC) setLastWrittenConvID(ctx context.Context, body string) error { 690 c.convLock.Lock() 691 defer c.convLock.Unlock() 692 693 msg, err := kbfsedits.ReadSelfWrite(body) 694 if err != nil { 695 return err 696 } 697 c.vlog.CLogf( 698 ctx, libkb.VLog1, "Last self-written conversation is %s", msg.ConvID) 699 c.lastWrittenConvID = msg.ConvID 700 return nil 701 } 702 703 // NewChatActivity implements the chat1.NotifyChatInterface for 704 // ChatRPC. 705 func (c *ChatRPC) NewChatActivity( 706 ctx context.Context, arg chat1.NewChatActivityArg) error { 707 activityType, err := arg.Activity.ActivityType() 708 if err != nil { 709 return err 710 } 711 switch activityType { 712 case chat1.ChatActivityType_NEW_CONVERSATION: 713 // If we learn about a new conversation for a given TLF, 714 // attempt to route it to the TLF. 715 info := arg.Activity.NewConversation() 716 err := c.newNotificationChannel(ctx, info.ConvID, info.Conv) 717 if err != nil { 718 return err 719 } 720 case chat1.ChatActivityType_INCOMING_MESSAGE: 721 // If we learn about a new message for a given conversation ID, 722 // let any registered callbacks for that conversation ID know. 723 msg := arg.Activity.IncomingMessage() 724 state, err := msg.Message.State() 725 if err != nil { 726 return err 727 } 728 if state != chat1.MessageUnboxedState_VALID { 729 return nil 730 } 731 732 validMsg := msg.Message.Valid() 733 msgType, err := validMsg.MessageBody.MessageType() 734 if err != nil { 735 return err 736 } 737 if msgType != chat1.MessageType_TEXT { 738 return nil 739 } 740 body := validMsg.MessageBody.Text().Body 741 742 c.convLock.RLock() 743 cbs := c.convCBs[msg.ConvID.ConvIDStr()] 744 c.convLock.RUnlock() 745 746 // If this is on the self-write channel, cache it and we're 747 // done. 748 selfConvID, _ := c.getSelfConvInfoIfCached() 749 if selfConvID.Eq(msg.ConvID) { 750 return c.setLastWrittenConvID(ctx, body) 751 } 752 753 if len(cbs) == 0 { 754 // No one is listening for this channel yet, so consider 755 // it a new channel. 756 err := c.newNotificationChannel(ctx, msg.ConvID, msg.Conv) 757 if err != nil { 758 return err 759 } 760 } else { 761 for _, cb := range cbs { 762 cb(msg.ConvID, body) 763 } 764 } 765 766 } 767 return nil 768 } 769 770 // ChatIdentifyUpdate implements the chat1.NotifyChatInterface for 771 // ChatRPC. 772 func (c *ChatRPC) ChatIdentifyUpdate( 773 _ context.Context, _ keybase1.CanonicalTLFNameAndIDWithBreaks) error { 774 return nil 775 } 776 777 // ChatTLFFinalize implements the chat1.NotifyChatInterface for 778 // ChatRPC. 779 func (c *ChatRPC) ChatTLFFinalize( 780 _ context.Context, _ chat1.ChatTLFFinalizeArg) error { 781 return nil 782 } 783 784 // ChatTLFResolve implements the chat1.NotifyChatInterface for 785 // ChatRPC. 786 func (c *ChatRPC) ChatTLFResolve( 787 _ context.Context, _ chat1.ChatTLFResolveArg) error { 788 return nil 789 } 790 791 // ChatInboxStale implements the chat1.NotifyChatInterface for 792 // ChatRPC. 793 func (c *ChatRPC) ChatInboxStale(_ context.Context, _ keybase1.UID) error { 794 return nil 795 } 796 797 // ChatThreadsStale implements the chat1.NotifyChatInterface for 798 // ChatRPC. 799 func (c *ChatRPC) ChatThreadsStale( 800 _ context.Context, _ chat1.ChatThreadsStaleArg) error { 801 return nil 802 } 803 804 // ChatTypingUpdate implements the chat1.NotifyChatInterface for 805 // ChatRPC. 806 func (c *ChatRPC) ChatTypingUpdate( 807 _ context.Context, _ []chat1.ConvTypingUpdate) error { 808 return nil 809 } 810 811 // ChatJoinedConversation implements the chat1.NotifyChatInterface for 812 // ChatRPC. 813 func (c *ChatRPC) ChatJoinedConversation( 814 _ context.Context, _ chat1.ChatJoinedConversationArg) error { 815 return nil 816 } 817 818 // ChatLeftConversation implements the chat1.NotifyChatInterface for 819 // ChatRPC. 820 func (c *ChatRPC) ChatLeftConversation( 821 _ context.Context, _ chat1.ChatLeftConversationArg) error { 822 return nil 823 } 824 825 // ChatResetConversation implements the chat1.NotifyChatInterface for 826 // ChatRPC. 827 func (c *ChatRPC) ChatResetConversation( 828 _ context.Context, _ chat1.ChatResetConversationArg) error { 829 return nil 830 } 831 832 // ChatInboxSyncStarted implements the chat1.NotifyChatInterface for 833 // ChatRPC. 834 func (c *ChatRPC) ChatInboxSyncStarted( 835 _ context.Context, _ keybase1.UID) error { 836 return nil 837 } 838 839 // ChatInboxSynced implements the chat1.NotifyChatInterface for 840 // ChatRPC. 841 func (c *ChatRPC) ChatInboxSynced( 842 _ context.Context, _ chat1.ChatInboxSyncedArg) error { 843 return nil 844 } 845 846 // ChatSetConvRetention implements the chat1.NotifyChatInterface for 847 // ChatRPC. 848 func (c *ChatRPC) ChatSetConvRetention( 849 _ context.Context, _ chat1.ChatSetConvRetentionArg) error { 850 return nil 851 } 852 853 // ChatSetTeamRetention implements the chat1.NotifyChatInterface for 854 // ChatRPC. 855 func (c *ChatRPC) ChatSetTeamRetention( 856 _ context.Context, _ chat1.ChatSetTeamRetentionArg) error { 857 return nil 858 } 859 860 // ChatSetConvSettings implements the chat1.NotifyChatInterface for 861 // ChatRPC. 862 func (c *ChatRPC) ChatSetConvSettings( 863 _ context.Context, _ chat1.ChatSetConvSettingsArg) error { 864 return nil 865 } 866 867 // ChatSubteamRename implements the chat1.NotifyChatInterface for 868 // ChatRPC. 869 func (c *ChatRPC) ChatSubteamRename( 870 _ context.Context, _ chat1.ChatSubteamRenameArg) error { 871 return nil 872 } 873 874 // ChatKBFSToImpteamUpgrade implements the chat1.NotifyChatInterface 875 // for ChatRPC. 876 func (c *ChatRPC) ChatKBFSToImpteamUpgrade( 877 _ context.Context, _ chat1.ChatKBFSToImpteamUpgradeArg) error { 878 return nil 879 } 880 881 // ChatAttachmentUploadStart implements the chat1.NotifyChatInterface 882 // for ChatRPC. 883 func (c *ChatRPC) ChatAttachmentUploadStart( 884 _ context.Context, _ chat1.ChatAttachmentUploadStartArg) error { 885 return nil 886 } 887 888 // ChatAttachmentUploadProgress implements the chat1.NotifyChatInterface 889 // for ChatRPC. 890 func (c *ChatRPC) ChatAttachmentUploadProgress( 891 _ context.Context, _ chat1.ChatAttachmentUploadProgressArg) error { 892 return nil 893 } 894 895 // ChatAttachmentDownloadProgress implements the chat1.NotifyChatInterface 896 // for ChatRPC. 897 func (c *ChatRPC) ChatAttachmentDownloadProgress( 898 _ context.Context, _ chat1.ChatAttachmentDownloadProgressArg) error { 899 return nil 900 } 901 902 // ChatAttachmentDownloadComplete implements the chat1.NotifyChatInterface 903 // for ChatRPC. 904 func (c *ChatRPC) ChatAttachmentDownloadComplete( 905 _ context.Context, _ chat1.ChatAttachmentDownloadCompleteArg) error { 906 return nil 907 } 908 909 // ChatArchiveProgress implements the chat1.NotifyChatInterface 910 // for ChatRPC. 911 func (c *ChatRPC) ChatArchiveProgress( 912 _ context.Context, _ chat1.ChatArchiveProgressArg) error { 913 return nil 914 } 915 916 // ChatArchiveComplete implements the chat1.NotifyChatInterface 917 // for ChatRPC. 918 func (c *ChatRPC) ChatArchiveComplete( 919 _ context.Context, _ chat1.ArchiveJobID) error { 920 return nil 921 } 922 923 // ChatPaymentInfo implements the chat1.NotifyChatInterface 924 // for ChatRPC. 925 func (c *ChatRPC) ChatPaymentInfo( 926 _ context.Context, _ chat1.ChatPaymentInfoArg) error { 927 return nil 928 } 929 930 // ChatRequestInfo implements the chat1.NotifyChatInterface 931 // for ChatRPC. 932 func (c *ChatRPC) ChatRequestInfo( 933 _ context.Context, _ chat1.ChatRequestInfoArg) error { 934 return nil 935 } 936 937 // ChatPromptUnfurl implements the chat1.NotifyChatInterface 938 // for ChatRPC. 939 func (c *ChatRPC) ChatPromptUnfurl(_ context.Context, _ chat1.ChatPromptUnfurlArg) error { 940 return nil 941 } 942 943 // ChatConvUpdate implements the chat1.NotifyChatInterface for 944 // ChatRPC. 945 func (c *ChatRPC) ChatConvUpdate( 946 _ context.Context, _ chat1.ChatConvUpdateArg) error { 947 return nil 948 } 949 950 // ChatWelcomeMessageLoaded implements the chat1.NotifyChatInterface for 951 // ChatRPC. 952 func (c *ChatRPC) ChatWelcomeMessageLoaded( 953 _ context.Context, _ chat1.ChatWelcomeMessageLoadedArg) error { 954 return nil 955 } 956 957 // ChatParticipantsInfo is the greatest function ever written 958 func (c *ChatRPC) ChatParticipantsInfo(context.Context, map[chat1.ConvIDStr][]chat1.UIParticipant) error { 959 return nil 960 }