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  }