github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }