github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/notify_router.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/protocol/chat1"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	stellar1 "github.com/keybase/client/go/protocol/stellar1"
    14  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    15  	context "golang.org/x/net/context"
    16  )
    17  
    18  // NotifyListener provides hooks for listening for when
    19  // notifications are called.  It is intended to simplify
    20  // testing notifications.
    21  type NotifyListener interface {
    22  	Logout()
    23  	Login(username string)
    24  	ClientOutOfDate(to, uri, msg string)
    25  	UserChanged(uid keybase1.UID)
    26  	TrackingChanged(uid keybase1.UID, username NormalizedUsername)
    27  	TrackingInfo(uid keybase1.UID, followers, followees []string)
    28  	FSOnlineStatusChanged(online bool)
    29  	FSActivity(activity keybase1.FSNotification)
    30  	FSPathUpdated(path string)
    31  	FSEditListResponse(arg keybase1.FSEditListArg)
    32  	FSSyncStatusResponse(arg keybase1.FSSyncStatusArg)
    33  	FSSyncEvent(arg keybase1.FSPathSyncStatus)
    34  	FSEditListRequest(arg keybase1.FSEditListRequest)
    35  	FSOverallSyncStatusChanged(arg keybase1.FolderSyncStatus)
    36  	FSFavoritesChanged()
    37  	FavoritesChanged(uid keybase1.UID)
    38  	FSSubscriptionNotify(arg keybase1.FSSubscriptionNotifyArg)
    39  	FSSubscriptionNotifyPath(arg keybase1.FSSubscriptionNotifyPathArg)
    40  	SimpleFSArchiveStatusChanged(status keybase1.SimpleFSArchiveStatus)
    41  	PaperKeyCached(uid keybase1.UID, encKID keybase1.KID, sigKID keybase1.KID)
    42  	KeyfamilyChanged(uid keybase1.UID)
    43  	NewChatActivity(uid keybase1.UID, activity chat1.ChatActivity, source chat1.ChatActivitySource)
    44  	NewChatKBFSFileEditActivity(uid keybase1.UID, activity chat1.ChatActivity)
    45  	ChatIdentifyUpdate(update keybase1.CanonicalTLFNameAndIDWithBreaks)
    46  	ChatTLFFinalize(uid keybase1.UID, convID chat1.ConversationID,
    47  		finalizeInfo chat1.ConversationFinalizeInfo)
    48  	ChatTLFResolve(uid keybase1.UID, convID chat1.ConversationID,
    49  		resolveInfo chat1.ConversationResolveInfo)
    50  	ChatInboxStale(uid keybase1.UID)
    51  	ChatThreadsStale(uid keybase1.UID, updates []chat1.ConversationStaleUpdate)
    52  	ChatInboxSynced(uid keybase1.UID, topicType chat1.TopicType, syncRes chat1.ChatSyncResult)
    53  	ChatInboxSyncStarted(uid keybase1.UID)
    54  	ChatTypingUpdate([]chat1.ConvTypingUpdate)
    55  	ChatJoinedConversation(uid keybase1.UID, convID chat1.ConversationID, conv *chat1.InboxUIItem)
    56  	ChatLeftConversation(uid keybase1.UID, convID chat1.ConversationID)
    57  	ChatResetConversation(uid keybase1.UID, convID chat1.ConversationID)
    58  	ChatSetConvRetention(uid keybase1.UID, convID chat1.ConversationID)
    59  	ChatSetTeamRetention(uid keybase1.UID, teamID keybase1.TeamID)
    60  	ChatSetConvSettings(uid keybase1.UID, convID chat1.ConversationID)
    61  	ChatSubteamRename(uid keybase1.UID, convIDs []chat1.ConversationID)
    62  	ChatKBFSToImpteamUpgrade(uid keybase1.UID, convID chat1.ConversationID)
    63  	ChatAttachmentUploadStart(uid keybase1.UID, convID chat1.ConversationID, outboxID chat1.OutboxID)
    64  	ChatAttachmentUploadProgress(uid keybase1.UID, convID chat1.ConversationID, outboxID chat1.OutboxID,
    65  		bytesComplete, bytesTotal int64)
    66  	ChatAttachmentDownloadProgress(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID,
    67  		bytesComplete, bytesTotal int64)
    68  	ChatAttachmentDownloadComplete(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID)
    69  	ChatArchiveProgress(jobID chat1.ArchiveJobID,
    70  		messagesComplete, messagesTotal int64)
    71  	ChatArchiveComplete(jobID chat1.ArchiveJobID)
    72  	ChatPaymentInfo(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIPaymentInfo)
    73  	ChatRequestInfo(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIRequestInfo)
    74  	ChatPromptUnfurl(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, domain string)
    75  	ChatConvUpdate(uid keybase1.UID, convID chat1.ConversationID)
    76  	ChatWelcomeMessageLoaded(teamID keybase1.TeamID, message chat1.WelcomeMessageDisplay)
    77  	ChatParticipantsInfo(participants map[chat1.ConvIDStr][]chat1.UIParticipant)
    78  	PGPKeyInSecretStoreFile()
    79  	BadgeState(badgeState keybase1.BadgeState)
    80  	ReachabilityChanged(r keybase1.Reachability)
    81  	TeamChangedByID(teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource)
    82  	TeamChangedByName(teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource)
    83  	TeamDeleted(teamID keybase1.TeamID)
    84  	TeamRoleMapChanged(version keybase1.UserTeamVersion)
    85  	UserBlocked(b keybase1.UserBlockedBody)
    86  	TeamExit(teamID keybase1.TeamID)
    87  	NewlyAddedToTeam(teamID keybase1.TeamID)
    88  	NewTeamEK(teamID keybase1.TeamID, generation keybase1.EkGeneration)
    89  	NewTeambotEK(teamID keybase1.TeamID, generation keybase1.EkGeneration)
    90  	TeambotEKNeeded(teamID keybase1.TeamID, botUID keybase1.UID, generation keybase1.EkGeneration, forceCreateGen *keybase1.EkGeneration)
    91  	NewTeambotKey(teamID keybase1.TeamID, generation keybase1.TeambotKeyGeneration)
    92  	TeambotKeyNeeded(teamID keybase1.TeamID, botUID keybase1.UID, generation keybase1.TeambotKeyGeneration)
    93  	AvatarUpdated(name string, formats []keybase1.AvatarFormat)
    94  	DeviceCloneCountChanged(newClones int)
    95  	WalletPaymentNotification(accountID stellar1.AccountID, paymentID stellar1.PaymentID)
    96  	WalletPaymentStatusNotification(accountID stellar1.AccountID, paymentID stellar1.PaymentID)
    97  	WalletRequestStatusNotification(reqID stellar1.KeybaseRequestID)
    98  	WalletAccountDetailsUpdate(accountID stellar1.AccountID, account stellar1.WalletAccountLocal)
    99  	WalletAccountsUpdate(accounts []stellar1.WalletAccountLocal)
   100  	WalletPendingPaymentsUpdate(accountID stellar1.AccountID, pending []stellar1.PaymentOrErrorLocal)
   101  	WalletRecentPaymentsUpdate(accountID stellar1.AccountID, firstPage stellar1.PaymentsPageLocal)
   102  	TeamMetadataUpdate()
   103  	CanUserPerformChanged(teamName string)
   104  	PhoneNumbersChanged(list []keybase1.UserPhoneNumber, category string, phoneNumber keybase1.PhoneNumber)
   105  	EmailAddressVerified(emailAddress keybase1.EmailAddress)
   106  	EmailsChanged(list []keybase1.Email, category string, email keybase1.EmailAddress)
   107  	PasswordChanged()
   108  	RootAuditError(msg string)
   109  	BoxAuditError(msg string)
   110  	RuntimeStatsUpdate(*keybase1.RuntimeStats)
   111  	HTTPSrvInfoUpdate(keybase1.HttpSrvInfo)
   112  	HandleKeybaseLink(link string, deferred bool)
   113  	IdentifyUpdate(okUsernames []string, brokenUsernames []string)
   114  	Reachability(keybase1.Reachability)
   115  	FeaturedBotsUpdate(bots []keybase1.FeaturedBot, limit, offset int)
   116  	SaltpackOperationStart(opType keybase1.SaltpackOperationType, filename string)
   117  	SaltpackOperationProgress(opType keybase1.SaltpackOperationType, filename string, bytesComplete, bytesTotal int64)
   118  	SaltpackOperationDone(opType keybase1.SaltpackOperationType, filename string)
   119  	UpdateInviteCounts(keybase1.InviteCounts)
   120  	TeamTreeMembershipsPartial(keybase1.TeamTreeMembership)
   121  	TeamTreeMembershipsDone(keybase1.TeamTreeMembershipsDoneResult)
   122  	WebOfTrustChanged(username string)
   123  }
   124  
   125  type NoopNotifyListener struct{}
   126  
   127  var _ NotifyListener = (*NoopNotifyListener)(nil)
   128  
   129  func (n *NoopNotifyListener) Logout()                                                       {}
   130  func (n *NoopNotifyListener) Login(username string)                                         {}
   131  func (n *NoopNotifyListener) ClientOutOfDate(to, uri, msg string)                           {}
   132  func (n *NoopNotifyListener) UserChanged(uid keybase1.UID)                                  {}
   133  func (n *NoopNotifyListener) TrackingChanged(uid keybase1.UID, username NormalizedUsername) {}
   134  func (n *NoopNotifyListener) TrackingInfo(uid keybase1.UID, followers, followees []string)  {}
   135  func (n *NoopNotifyListener) FSOnlineStatusChanged(online bool)                             {}
   136  func (n *NoopNotifyListener) FSOverallSyncStatusChanged(status keybase1.FolderSyncStatus)   {}
   137  func (n *NoopNotifyListener) FSFavoritesChanged()                                           {}
   138  func (n *NoopNotifyListener) FSActivity(activity keybase1.FSNotification)                   {}
   139  func (n *NoopNotifyListener) FSPathUpdated(path string)                                     {}
   140  func (n *NoopNotifyListener) FSEditListResponse(arg keybase1.FSEditListArg)                 {}
   141  func (n *NoopNotifyListener) FSSyncStatusResponse(arg keybase1.FSSyncStatusArg)             {}
   142  func (n *NoopNotifyListener) FSSyncEvent(arg keybase1.FSPathSyncStatus)                     {}
   143  func (n *NoopNotifyListener) FSEditListRequest(arg keybase1.FSEditListRequest)              {}
   144  func (n *NoopNotifyListener) FavoritesChanged(uid keybase1.UID)                             {}
   145  func (n *NoopNotifyListener) FSSubscriptionNotify(arg keybase1.FSSubscriptionNotifyArg) {
   146  }
   147  func (n *NoopNotifyListener) FSSubscriptionNotifyPath(arg keybase1.FSSubscriptionNotifyPathArg) {
   148  }
   149  func (n *NoopNotifyListener) SimpleFSArchiveStatusChanged(status keybase1.SimpleFSArchiveStatus) {
   150  }
   151  func (n *NoopNotifyListener) PaperKeyCached(uid keybase1.UID, encKID keybase1.KID, sigKID keybase1.KID) {
   152  }
   153  func (n *NoopNotifyListener) KeyfamilyChanged(uid keybase1.UID) {}
   154  func (n *NoopNotifyListener) NewChatActivity(uid keybase1.UID, activity chat1.ChatActivity,
   155  	source chat1.ChatActivitySource) {
   156  }
   157  func (n *NoopNotifyListener) NewChatKBFSFileEditActivity(uid keybase1.UID, activity chat1.ChatActivity) {
   158  }
   159  func (n *NoopNotifyListener) ChatIdentifyUpdate(update keybase1.CanonicalTLFNameAndIDWithBreaks) {}
   160  func (n *NoopNotifyListener) ChatTLFFinalize(uid keybase1.UID, convID chat1.ConversationID,
   161  	finalizeInfo chat1.ConversationFinalizeInfo) {
   162  }
   163  func (n *NoopNotifyListener) ChatTLFResolve(uid keybase1.UID, convID chat1.ConversationID,
   164  	resolveInfo chat1.ConversationResolveInfo) {
   165  }
   166  func (n *NoopNotifyListener) ChatInboxStale(uid keybase1.UID) {}
   167  func (n *NoopNotifyListener) ChatThreadsStale(uid keybase1.UID, updates []chat1.ConversationStaleUpdate) {
   168  }
   169  func (n *NoopNotifyListener) ChatInboxSynced(uid keybase1.UID, topicType chat1.TopicType,
   170  	syncRes chat1.ChatSyncResult) {
   171  }
   172  func (n *NoopNotifyListener) ChatInboxSyncStarted(uid keybase1.UID)     {}
   173  func (n *NoopNotifyListener) ChatTypingUpdate([]chat1.ConvTypingUpdate) {}
   174  func (n *NoopNotifyListener) ChatJoinedConversation(uid keybase1.UID, convID chat1.ConversationID,
   175  	conv *chat1.InboxUIItem) {
   176  }
   177  func (n *NoopNotifyListener) ChatLeftConversation(uid keybase1.UID, convID chat1.ConversationID)  {}
   178  func (n *NoopNotifyListener) ChatResetConversation(uid keybase1.UID, convID chat1.ConversationID) {}
   179  func (n *NoopNotifyListener) Chat(uid keybase1.UID, convID chat1.ConversationID)                  {}
   180  func (n *NoopNotifyListener) ChatSetConvRetention(uid keybase1.UID, convID chat1.ConversationID)  {}
   181  func (n *NoopNotifyListener) ChatSetTeamRetention(uid keybase1.UID, teamID keybase1.TeamID)       {}
   182  func (n *NoopNotifyListener) ChatSetConvSettings(uid keybase1.UID, convID chat1.ConversationID)   {}
   183  func (n *NoopNotifyListener) ChatSubteamRename(uid keybase1.UID, convIDs []chat1.ConversationID)  {}
   184  func (n *NoopNotifyListener) ChatKBFSToImpteamUpgrade(uid keybase1.UID, convID chat1.ConversationID) {
   185  }
   186  func (n *NoopNotifyListener) ChatAttachmentUploadStart(uid keybase1.UID, convID chat1.ConversationID,
   187  	outboxID chat1.OutboxID) {
   188  }
   189  func (n *NoopNotifyListener) ChatAttachmentUploadProgress(uid keybase1.UID, convID chat1.ConversationID,
   190  	outboxID chat1.OutboxID, bytesComplete, bytesTotal int64) {
   191  }
   192  func (n *NoopNotifyListener) ChatAttachmentDownloadProgress(uid keybase1.UID, convID chat1.ConversationID,
   193  	msgID chat1.MessageID, bytesComplete, bytesTotal int64) {
   194  }
   195  func (n *NoopNotifyListener) ChatAttachmentDownloadComplete(uid keybase1.UID, convID chat1.ConversationID,
   196  	msgID chat1.MessageID) {
   197  }
   198  func (n *NoopNotifyListener) ChatArchiveProgress(jobID chat1.ArchiveJobID, messagesComplete, messagesTotal int64) {
   199  }
   200  func (n *NoopNotifyListener) ChatArchiveComplete(jobID chat1.ArchiveJobID) {
   201  }
   202  func (n *NoopNotifyListener) ChatPaymentInfo(uid keybase1.UID, convID chat1.ConversationID,
   203  	msgID chat1.MessageID, info chat1.UIPaymentInfo) {
   204  }
   205  func (n *NoopNotifyListener) ChatRequestInfo(uid keybase1.UID, convID chat1.ConversationID,
   206  	msgID chat1.MessageID, info chat1.UIRequestInfo) {
   207  }
   208  func (n *NoopNotifyListener) ChatPromptUnfurl(uid keybase1.UID, convID chat1.ConversationID,
   209  	msgID chat1.MessageID, domain string) {
   210  }
   211  func (n *NoopNotifyListener) ChatConvUpdate(uid keybase1.UID, convID chat1.ConversationID)          {}
   212  func (n *NoopNotifyListener) ChatWelcomeMessageLoaded(keybase1.TeamID, chat1.WelcomeMessageDisplay) {}
   213  func (n *NoopNotifyListener) ChatParticipantsInfo(
   214  	participants map[chat1.ConvIDStr][]chat1.UIParticipant) {
   215  }
   216  
   217  func (n *NoopNotifyListener) PGPKeyInSecretStoreFile()                    {}
   218  func (n *NoopNotifyListener) BadgeState(badgeState keybase1.BadgeState)   {}
   219  func (n *NoopNotifyListener) ReachabilityChanged(r keybase1.Reachability) {}
   220  func (n *NoopNotifyListener) TeamChangedByID(teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
   221  }
   222  func (n *NoopNotifyListener) TeamChangedByName(teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
   223  }
   224  func (n *NoopNotifyListener) TeamDeleted(teamID keybase1.TeamID)                                    {}
   225  func (n *NoopNotifyListener) TeamExit(teamID keybase1.TeamID)                                       {}
   226  func (n *NoopNotifyListener) TeamRoleMapChanged(version keybase1.UserTeamVersion)                   {}
   227  func (n *NoopNotifyListener) NewTeamEK(teamID keybase1.TeamID, generation keybase1.EkGeneration)    {}
   228  func (n *NoopNotifyListener) NewTeambotEK(teamID keybase1.TeamID, generation keybase1.EkGeneration) {}
   229  func (n *NoopNotifyListener) TeambotEKNeeded(teamID keybase1.TeamID, botUID keybase1.UID,
   230  	generation keybase1.EkGeneration, forceCreateGen *keybase1.EkGeneration) {
   231  }
   232  func (n *NoopNotifyListener) NewTeambotKey(teamID keybase1.TeamID, generation keybase1.TeambotKeyGeneration) {
   233  }
   234  func (n *NoopNotifyListener) TeambotKeyNeeded(teamID keybase1.TeamID, botUID keybase1.UID,
   235  	generation keybase1.TeambotKeyGeneration) {
   236  }
   237  func (n *NoopNotifyListener) NewlyAddedToTeam(teamID keybase1.TeamID)                    {}
   238  func (n *NoopNotifyListener) AvatarUpdated(name string, formats []keybase1.AvatarFormat) {}
   239  func (n *NoopNotifyListener) DeviceCloneCountChanged(newClones int)                      {}
   240  func (n *NoopNotifyListener) WalletPaymentNotification(accountID stellar1.AccountID, paymentID stellar1.PaymentID) {
   241  }
   242  func (n *NoopNotifyListener) WalletPaymentStatusNotification(accountID stellar1.AccountID, paymentID stellar1.PaymentID) {
   243  }
   244  func (n *NoopNotifyListener) WalletRequestStatusNotification(reqID stellar1.KeybaseRequestID) {}
   245  func (n *NoopNotifyListener) WalletAccountDetailsUpdate(accountID stellar1.AccountID, account stellar1.WalletAccountLocal) {
   246  }
   247  func (n *NoopNotifyListener) WalletAccountsUpdate(accounts []stellar1.WalletAccountLocal) {}
   248  func (n *NoopNotifyListener) WalletPendingPaymentsUpdate(accountID stellar1.AccountID, pending []stellar1.PaymentOrErrorLocal) {
   249  }
   250  func (n *NoopNotifyListener) WalletRecentPaymentsUpdate(accountID stellar1.AccountID, firstPage stellar1.PaymentsPageLocal) {
   251  }
   252  func (n *NoopNotifyListener) TeamMetadataUpdate()                   {}
   253  func (n *NoopNotifyListener) CanUserPerformChanged(teamName string) {}
   254  func (n *NoopNotifyListener) PhoneNumbersChanged(list []keybase1.UserPhoneNumber, category string, phoneNumber keybase1.PhoneNumber) {
   255  }
   256  func (n *NoopNotifyListener) EmailAddressVerified(emailAddress keybase1.EmailAddress) {}
   257  func (n *NoopNotifyListener) EmailsChanged(list []keybase1.Email, category string, email keybase1.EmailAddress) {
   258  }
   259  func (n *NoopNotifyListener) PasswordChanged()                             {}
   260  func (n *NoopNotifyListener) RootAuditError(msg string)                    {}
   261  func (n *NoopNotifyListener) BoxAuditError(msg string)                     {}
   262  func (n *NoopNotifyListener) RuntimeStatsUpdate(*keybase1.RuntimeStats)    {}
   263  func (n *NoopNotifyListener) HTTPSrvInfoUpdate(keybase1.HttpSrvInfo)       {}
   264  func (n *NoopNotifyListener) HandleKeybaseLink(link string, deferred bool) {}
   265  func (n *NoopNotifyListener) IdentifyUpdate(okUsernames []string, brokenUsernames []string) {
   266  }
   267  func (n *NoopNotifyListener) Reachability(keybase1.Reachability)                                {}
   268  func (n *NoopNotifyListener) UserBlocked(keybase1.UserBlockedBody)                              {}
   269  func (n *NoopNotifyListener) FeaturedBotsUpdate(bots []keybase1.FeaturedBot, limit, offset int) {}
   270  func (n *NoopNotifyListener) SaltpackOperationStart(opType keybase1.SaltpackOperationType, filename string) {
   271  }
   272  func (n *NoopNotifyListener) SaltpackOperationProgress(opType keybase1.SaltpackOperationType, filename string, bytesComplete, bytesTotal int64) {
   273  }
   274  func (n *NoopNotifyListener) SaltpackOperationDone(opType keybase1.SaltpackOperationType, filename string) {
   275  }
   276  func (n *NoopNotifyListener) UpdateInviteCounts(keybase1.InviteCounts) {
   277  }
   278  func (n *NoopNotifyListener) TeamTreeMembershipsPartial(keybase1.TeamTreeMembership)         {}
   279  func (n *NoopNotifyListener) TeamTreeMembershipsDone(keybase1.TeamTreeMembershipsDoneResult) {}
   280  func (n *NoopNotifyListener) WebOfTrustChanged(username string) {
   281  }
   282  
   283  type NotifyListenerID string
   284  
   285  // NotifyRouter routes notifications to the various active RPC
   286  // connections. It's careful only to route to those who are interested
   287  type NotifyRouter struct {
   288  	sync.Mutex
   289  	Contextified
   290  	cm        *ConnectionManager
   291  	state     map[ConnectionID]keybase1.NotificationChannels
   292  	listeners map[NotifyListenerID]NotifyListener
   293  }
   294  
   295  // NewNotifyRouter makes a new notification router; we should only
   296  // make one of these per process.
   297  func NewNotifyRouter(g *GlobalContext) *NotifyRouter {
   298  	return &NotifyRouter{
   299  		Contextified: NewContextified(g),
   300  		cm:           g.ConnectionManager,
   301  		state:        make(map[ConnectionID]keybase1.NotificationChannels),
   302  		listeners:    make(map[NotifyListenerID]NotifyListener),
   303  	}
   304  }
   305  
   306  func (n *NotifyRouter) AddListener(listener NotifyListener) NotifyListenerID {
   307  	n.Lock()
   308  	defer n.Unlock()
   309  	id := NotifyListenerID(RandStringB64(3))
   310  	n.listeners[id] = listener
   311  	return id
   312  }
   313  
   314  func (n *NotifyRouter) RemoveListener(id NotifyListenerID) {
   315  	n.Lock()
   316  	defer n.Unlock()
   317  	delete(n.listeners, id)
   318  }
   319  
   320  func (n *NotifyRouter) Shutdown() {}
   321  
   322  func (n *NotifyRouter) setNotificationChannels(id ConnectionID, val keybase1.NotificationChannels) {
   323  	n.Lock()
   324  	defer n.Unlock()
   325  	n.state[id] = val
   326  }
   327  
   328  func (n *NotifyRouter) getNotificationChannels(id ConnectionID) keybase1.NotificationChannels {
   329  	n.Lock()
   330  	defer n.Unlock()
   331  	return n.state[id]
   332  }
   333  
   334  // GetChannels retrieves which notification channels a connection is interested it
   335  // given its ID.
   336  func (n *NotifyRouter) GetChannels(i ConnectionID) keybase1.NotificationChannels {
   337  	return n.getNotificationChannels(i)
   338  }
   339  
   340  func (n *NotifyRouter) runListeners(f func(listener NotifyListener)) {
   341  	var listeners []NotifyListener
   342  	n.Lock()
   343  	for _, l := range n.listeners {
   344  		listeners = append(listeners, l)
   345  	}
   346  	n.Unlock()
   347  	for _, l := range listeners {
   348  		f(l)
   349  	}
   350  }
   351  
   352  // AddConnection should be called every time there's a new RPC connection
   353  // established for this server.  The caller should pass in the Transporter
   354  // and also the channel that will get messages when the channel closes.
   355  func (n *NotifyRouter) AddConnection(xp rpc.Transporter, ch chan error) ConnectionID {
   356  	if n == nil {
   357  		return 0
   358  	}
   359  	id := n.cm.AddConnection(xp, ch)
   360  	n.setNotificationChannels(id, keybase1.NotificationChannels{})
   361  	return id
   362  }
   363  
   364  // SetChannels sets which notification channels are interested for the connection
   365  // with the given connection ID.
   366  func (n *NotifyRouter) SetChannels(i ConnectionID, nc keybase1.NotificationChannels) {
   367  	n.setNotificationChannels(i, nc)
   368  }
   369  
   370  // HandleLogout is called whenever the current user logged out. It will broadcast
   371  // the message to all connections who care about such a message.
   372  func (n *NotifyRouter) HandleLogout(ctx context.Context) {
   373  	if n == nil {
   374  		return
   375  	}
   376  	defer n.G().CTrace(ctx, "NotifyRouter#HandleLogout", nil)()
   377  	ctx = CopyTagsToBackground(ctx)
   378  	// For all connections we currently have open...
   379  	n.cm.ApplyAllDetails(func(id ConnectionID, xp rpc.Transporter, d *keybase1.ClientDetails) bool {
   380  		// If the connection wants the `Session` notification type
   381  		registered := false
   382  		if n.getNotificationChannels(id).Session {
   383  			registered = true
   384  			// In the background do...
   385  			go func() {
   386  				// A send of a `LoggedOut` RPC
   387  				_ = (keybase1.NotifySessionClient{
   388  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   389  				}).LoggedOut(ctx)
   390  			}()
   391  		}
   392  		desc := "<nil>"
   393  		if d != nil {
   394  			desc = fmt.Sprintf("%+v", *d)
   395  		}
   396  		n.G().Log.CDebugf(ctx, "| NotifyRouter#HandleLogout: client %s (sent=%v)", desc, registered)
   397  		return true
   398  	})
   399  
   400  	n.runListeners(func(listener NotifyListener) {
   401  		listener.Logout()
   402  	})
   403  }
   404  
   405  // HandleLogin is called when a user logs in.
   406  func (n *NotifyRouter) HandleLogin(ctx context.Context, u string) {
   407  	if n == nil {
   408  		return
   409  	}
   410  	n.G().Log.CDebugf(ctx, "+ Sending login notification for user %q", u)
   411  	n.SendLogin(ctx, u, false)
   412  }
   413  
   414  // HandleSignup is called when a user is signed up. It will broadcast a loggedIn
   415  // notification with a flag to signify this was because of a signup.
   416  func (n *NotifyRouter) HandleSignup(ctx context.Context, u string) {
   417  	if n == nil {
   418  		return
   419  	}
   420  	n.G().Log.CDebugf(ctx, "+ Sending login notification for signup, as user %q", u)
   421  	n.SendLogin(ctx, u, true)
   422  }
   423  
   424  // SendLogin is called whenever a user logs in. It will broadcast
   425  // the message to all connections who care about such a message.
   426  func (n *NotifyRouter) SendLogin(ctx context.Context, u string, signedUp bool) {
   427  	if n == nil {
   428  		return
   429  	}
   430  	n.G().Log.CDebugf(ctx, "+ Sending login notification, as user %q, signedUp %t", u, signedUp)
   431  	// For all connections we currently have open...
   432  	ctx = CopyTagsToBackground(ctx)
   433  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   434  		// If the connection wants the `Session` notification type
   435  		if n.getNotificationChannels(id).Session {
   436  			// In the background do...
   437  			go func() {
   438  				// A send of a `LoggedIn` RPC
   439  				_ = (keybase1.NotifySessionClient{
   440  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   441  				}).LoggedIn(ctx, keybase1.LoggedInArg{
   442  					Username: u,
   443  					SignedUp: signedUp,
   444  				})
   445  			}()
   446  		}
   447  		return true
   448  	})
   449  
   450  	n.runListeners(func(listener NotifyListener) {
   451  		listener.Login(u)
   452  	})
   453  	n.G().Log.CDebugf(ctx, "- Login notification sent")
   454  }
   455  
   456  // ClientOutOfDate is called whenever the API server tells us our client is out
   457  // of date. (This is done by adding special headers to every API response that
   458  // an out-of-date client makes.)
   459  func (n *NotifyRouter) HandleClientOutOfDate(upgradeTo, upgradeURI, upgradeMsg string) {
   460  	if n == nil {
   461  		return
   462  	}
   463  	n.G().Log.Debug("+ Sending client-out-of-date notification")
   464  	// For all connections we currently have open...
   465  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   466  		// If the connection wants the `Session` notification type
   467  		if n.getNotificationChannels(id).Session {
   468  			// In the background do...
   469  			go func() {
   470  				// A send of a `ClientOutOfDate` RPC
   471  				_ = (keybase1.NotifySessionClient{
   472  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   473  				}).ClientOutOfDate(context.Background(), keybase1.ClientOutOfDateArg{
   474  					UpgradeTo:  upgradeTo,
   475  					UpgradeURI: upgradeURI,
   476  					UpgradeMsg: upgradeMsg,
   477  				})
   478  			}()
   479  		}
   480  		return true
   481  	})
   482  	n.runListeners(func(listener NotifyListener) {
   483  		listener.ClientOutOfDate(upgradeTo, upgradeURI, upgradeMsg)
   484  	})
   485  	n.G().Log.Debug("- client-out-of-date notification sent")
   486  }
   487  
   488  // HandleUserChanged is called whenever we know that a given user has
   489  // changed (and must be cache-busted). It will broadcast the messages
   490  // to all curious listeners. NOTE: we now only do this for the current logged in user
   491  func (n *NotifyRouter) HandleUserChanged(mctx MetaContext, uid keybase1.UID, reason string) {
   492  	if !mctx.G().GetMyUID().Equal(uid) {
   493  		// don't send these for anyone but the current logged in user, no one cares about anything
   494  		// about other users
   495  		return
   496  	}
   497  	mctx.Debug("Sending UserChanged notification %v '%v')", uid, reason)
   498  	if n == nil {
   499  		return
   500  	}
   501  	// For all connections we currently have open...
   502  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   503  		// If the connection wants the `Users` notification type
   504  		if n.getNotificationChannels(id).Users {
   505  			// In the background do...
   506  			go func() {
   507  				// A send of a `UserChanged` RPC with the user's UID
   508  				_ = (keybase1.NotifyUsersClient{
   509  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(mctx.G()), nil),
   510  				}).UserChanged(context.Background(), uid)
   511  			}()
   512  		}
   513  		return true
   514  	})
   515  	n.runListeners(func(listener NotifyListener) {
   516  		listener.UserChanged(uid)
   517  	})
   518  }
   519  
   520  // HandleTrackingChanged is called whenever we have a new tracking or
   521  // untracking chain link related to a given user. It will broadcast the
   522  // messages to all curious listeners.
   523  // isTracking is set to true if current user is tracking uid.
   524  func (n *NotifyRouter) HandleTrackingChanged(uid keybase1.UID, username NormalizedUsername, isTracking bool) {
   525  	if n == nil {
   526  		return
   527  	}
   528  	arg := keybase1.TrackingChangedArg{
   529  		Uid:        uid,
   530  		Username:   username.String(),
   531  		IsTracking: isTracking,
   532  	}
   533  	// For all connections we currently have open...
   534  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   535  		// If the connection wants the `Tracking` notification type
   536  		if n.getNotificationChannels(id).Tracking {
   537  			// In the background do...
   538  			go func() {
   539  				// A send of a `TrackingChanged` RPC with the user's UID
   540  				_ = (keybase1.NotifyTrackingClient{
   541  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   542  				}).TrackingChanged(context.Background(), arg)
   543  			}()
   544  		}
   545  		return true
   546  	})
   547  	n.runListeners(func(listener NotifyListener) {
   548  		listener.TrackingChanged(uid, username)
   549  	})
   550  }
   551  
   552  func (n *NotifyRouter) HandleWebOfTrustChanged(username string) {
   553  	if n == nil {
   554  		return
   555  	}
   556  	// For all connections we currently have open...
   557  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   558  		// If the connection wants the notification type
   559  		if n.getNotificationChannels(id).Tracking {
   560  			// In the background do...
   561  			go func() {
   562  				// A send of a `WebOfTrustChanged` RPC with the user's username
   563  				_ = (keybase1.NotifyUsersClient{
   564  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   565  				}).WebOfTrustChanged(context.Background(), username)
   566  			}()
   567  		}
   568  		return true
   569  	})
   570  	n.runListeners(func(listener NotifyListener) {
   571  		listener.WebOfTrustChanged(username)
   572  	})
   573  }
   574  
   575  func (n *NotifyRouter) HandleTrackingInfo(arg keybase1.TrackingInfoArg) {
   576  	if n == nil {
   577  		return
   578  	}
   579  	// For all connections we currently have open...
   580  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   581  		// If the connection wants the `Tracking` notification type
   582  		if n.getNotificationChannels(id).Tracking {
   583  			// In the background do...
   584  			go func() {
   585  				// A send of a `TrackingInfo` RPC with the user's UID
   586  				_ = (keybase1.NotifyTrackingClient{
   587  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   588  				}).TrackingInfo(context.Background(), arg)
   589  			}()
   590  		}
   591  		return true
   592  	})
   593  	n.runListeners(func(listener NotifyListener) {
   594  		listener.TrackingInfo(arg.Uid, arg.Followers, arg.Followees)
   595  	})
   596  }
   597  
   598  // HandleBadgeState is called whenever the badge state changes
   599  // It will broadcast the messages to all curious listeners.
   600  func (n *NotifyRouter) HandleBadgeState(badgeState keybase1.BadgeState) {
   601  	if n == nil {
   602  		return
   603  	}
   604  	n.G().Log.Debug("Sending BadgeState notification")
   605  	// For all connections we currently have open...
   606  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   607  		// If the connection wants the `Badges` notification type
   608  		if n.getNotificationChannels(id).Badges {
   609  			// In the background do...
   610  			go func() {
   611  				// A send of a `BadgeState` RPC with the badge state
   612  				_ = (keybase1.NotifyBadgesClient{
   613  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   614  				}).BadgeState(context.Background(), badgeState)
   615  			}()
   616  		}
   617  		return true
   618  	})
   619  	n.runListeners(func(listener NotifyListener) {
   620  		listener.BadgeState(badgeState)
   621  	})
   622  }
   623  
   624  // HandleFSOnlineStatusChanged is called when KBFS's online status changes. It
   625  // will broadcast the messages to all curious listeners.
   626  func (n *NotifyRouter) HandleFSOnlineStatusChanged(online bool) {
   627  	if n == nil {
   628  		return
   629  	}
   630  	// For all connections we currently have open...
   631  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   632  		// If the connection wants the `kbfs` notification type
   633  		if n.getNotificationChannels(id).Kbfs {
   634  			// In the background do...
   635  			go func() {
   636  				// A send of a `FSOnlineStatusChanged` RPC with the
   637  				// notification
   638  				_ = (keybase1.NotifyFSClient{
   639  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   640  				}).FSOnlineStatusChanged(context.Background(), online)
   641  			}()
   642  		}
   643  		return true
   644  	})
   645  	n.runListeners(func(listener NotifyListener) {
   646  		listener.FSOnlineStatusChanged(online)
   647  	})
   648  }
   649  
   650  // HandleFSOverallSyncStatusChanged is called when the overall sync status
   651  // changes. It will broadcast the messages to all curious listeners.
   652  func (n *NotifyRouter) HandleFSOverallSyncStatusChanged(status keybase1.FolderSyncStatus) {
   653  	if n == nil {
   654  		return
   655  	}
   656  	// For all connections we currently have open...
   657  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   658  		// If the connection wants the `kbfs` notification type
   659  		if n.getNotificationChannels(id).Kbfs {
   660  			// In the background do...
   661  			go func() {
   662  				// A send of a `FSOnlineStatusChanged` RPC with the
   663  				// notification
   664  				_ = (keybase1.NotifyFSClient{
   665  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   666  				}).FSOverallSyncStatusChanged(context.Background(), status)
   667  			}()
   668  		}
   669  		return true
   670  	})
   671  	n.runListeners(func(listener NotifyListener) {
   672  		listener.FSOverallSyncStatusChanged(status)
   673  	})
   674  }
   675  
   676  // HandleFSFavoritesChanged is called when the overall sync status
   677  // changes. It will broadcast the messages to all curious listeners.
   678  func (n *NotifyRouter) HandleFSFavoritesChanged() {
   679  	if n == nil {
   680  		return
   681  	}
   682  	// For all connections we currently have open...
   683  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   684  		// If the connection wants the `kbfs` notification type
   685  		if n.getNotificationChannels(id).Kbfs {
   686  			// In the background do...
   687  			go func() {
   688  				// A send of a `FSFavoritesChanged` RPC with the
   689  				// notification
   690  				_ = (keybase1.NotifyFSClient{
   691  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   692  				}).FSFavoritesChanged(context.Background())
   693  			}()
   694  		}
   695  		return true
   696  	})
   697  	n.runListeners(func(listener NotifyListener) {
   698  		listener.FSFavoritesChanged()
   699  	})
   700  }
   701  
   702  // HandleFSActivity is called for any KBFS notification. It will broadcast the messages
   703  // to all curious listeners.
   704  func (n *NotifyRouter) HandleFSActivity(activity keybase1.FSNotification) {
   705  	if n == nil {
   706  		return
   707  	}
   708  	// For all connections we currently have open...
   709  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   710  		// If the connection wants the `Kbfsdesktop` notification type
   711  		if n.getNotificationChannels(id).Kbfsdesktop {
   712  			// In the background do...
   713  			go func() {
   714  				// A send of a `FSActivity` RPC with the notification
   715  				_ = (keybase1.NotifyFSClient{
   716  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   717  				}).FSActivity(context.Background(), activity)
   718  			}()
   719  		}
   720  		return true
   721  	})
   722  	n.runListeners(func(listener NotifyListener) {
   723  		listener.FSActivity(activity)
   724  	})
   725  }
   726  
   727  // HandleFSPathUpdated is called for any path update notification. It
   728  // will broadcast the messages to all curious listeners.
   729  func (n *NotifyRouter) HandleFSPathUpdated(path string) {
   730  	if n == nil {
   731  		return
   732  	}
   733  	// For all connections we currently have open...
   734  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   735  		// If the connection wants the `Kbfs` notification type
   736  		if n.getNotificationChannels(id).Kbfs {
   737  			// In the background do...
   738  			go func() {
   739  				// A send of a `FSPathUpdated` RPC with the notification
   740  				_ = (keybase1.NotifyFSClient{
   741  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   742  				}).FSPathUpdated(context.Background(), path)
   743  			}()
   744  		}
   745  		return true
   746  	})
   747  
   748  	n.runListeners(func(listener NotifyListener) {
   749  		listener.FSPathUpdated(path)
   750  	})
   751  }
   752  
   753  // HandleFSEditListResponse is called for KBFS edit list response notifications.
   754  func (n *NotifyRouter) HandleFSEditListResponse(ctx context.Context, arg keybase1.FSEditListArg) {
   755  	if n == nil {
   756  		return
   757  	}
   758  
   759  	// We have to make sure the context survives until all subsequent
   760  	// RPCs launched in this method are done.  So we wait until
   761  	// the last completes before returning.
   762  	var wg sync.WaitGroup
   763  
   764  	// For all connections we currently have open...
   765  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   766  		// If the connection wants the `Kbfslegacy` notification type
   767  		if n.getNotificationChannels(id).Kbfslegacy {
   768  			// In the background do...
   769  			wg.Add(1)
   770  			go func() {
   771  				// A send of a `FSEditListResponse` RPC with the notification
   772  				_ = (keybase1.NotifyFSClient{
   773  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   774  				}).FSEditListResponse(context.Background(), keybase1.FSEditListResponseArg(arg))
   775  				wg.Done()
   776  			}()
   777  		}
   778  		return true
   779  	})
   780  	wg.Wait()
   781  
   782  	n.runListeners(func(listener NotifyListener) {
   783  		listener.FSEditListResponse(arg)
   784  	})
   785  }
   786  
   787  // HandleFSEditListRequest is called for KBFS edit list request notifications.
   788  func (n *NotifyRouter) HandleFSEditListRequest(ctx context.Context, arg keybase1.FSEditListRequest) {
   789  	if n == nil {
   790  		return
   791  	}
   792  
   793  	// See above
   794  	var wg sync.WaitGroup
   795  
   796  	// For all connections we currently have open...
   797  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   798  		// If the connection wants the `Kbfslegacy` notification type
   799  		if n.getNotificationChannels(id).Kbfslegacy {
   800  			wg.Add(1)
   801  			// In the background do...
   802  			go func() {
   803  				// A send of a `FSEditListRequest` RPC with the notification
   804  				_ = (keybase1.NotifyFSRequestClient{
   805  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   806  				}).FSEditListRequest(context.Background(), arg)
   807  				wg.Done()
   808  			}()
   809  		}
   810  		return true
   811  	})
   812  
   813  	wg.Wait()
   814  
   815  	n.runListeners(func(listener NotifyListener) {
   816  		listener.FSEditListRequest(arg)
   817  	})
   818  }
   819  
   820  // HandleFSSyncStatus is called for KBFS sync status notifications.
   821  func (n *NotifyRouter) HandleFSSyncStatus(ctx context.Context, arg keybase1.FSSyncStatusArg) {
   822  	if n == nil {
   823  		return
   824  	}
   825  	// For all connections we currently have open...
   826  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   827  		// If the connection wants the `Kbfslegacy` notification type
   828  		if n.getNotificationChannels(id).Kbfslegacy {
   829  			// In the background do...
   830  			go func() {
   831  				// A send of a `FSSyncStatusResponse` RPC with the notification
   832  				_ = (keybase1.NotifyFSClient{
   833  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   834  				}).FSSyncStatusResponse(context.Background(), keybase1.FSSyncStatusResponseArg(arg))
   835  			}()
   836  		}
   837  		return true
   838  	})
   839  
   840  	n.runListeners(func(listener NotifyListener) {
   841  		listener.FSSyncStatusResponse(arg)
   842  	})
   843  }
   844  
   845  // HandleFSSyncEvent is called for KBFS sync event notifications.
   846  func (n *NotifyRouter) HandleFSSyncEvent(ctx context.Context, arg keybase1.FSPathSyncStatus) {
   847  	if n == nil {
   848  		return
   849  	}
   850  	// For all connections we currently have open...
   851  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   852  		// If the connection wants the `Kbfs` notification type
   853  		if n.getNotificationChannels(id).Kbfs {
   854  			// In the background do...
   855  			go func() {
   856  				// A send of a `FSSyncActivity` RPC with the notification
   857  				_ = (keybase1.NotifyFSClient{
   858  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   859  				}).FSSyncActivity(context.Background(), arg)
   860  			}()
   861  		}
   862  		return true
   863  	})
   864  	n.runListeners(func(listener NotifyListener) {
   865  		listener.FSSyncEvent(arg)
   866  	})
   867  }
   868  
   869  // HandleFavoritesChanged is called whenever the kbfs favorites change
   870  // for a user (and caches should be invalidated). It will broadcast the
   871  // messages to all curious listeners.
   872  func (n *NotifyRouter) HandleFavoritesChanged(uid keybase1.UID) {
   873  	if n == nil {
   874  		return
   875  	}
   876  
   877  	n.G().Log.Debug("+ Sending favorites changed notification")
   878  	// For all connections we currently have open...
   879  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   880  		// If the connection wants the `Favorites` notification type
   881  		if n.getNotificationChannels(id).Favorites {
   882  			// In the background do...
   883  			go func() {
   884  				// A send of a `FavoritesChanged` RPC with the user's UID
   885  				_ = (keybase1.NotifyFavoritesClient{
   886  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   887  				}).FavoritesChanged(context.Background(), uid)
   888  			}()
   889  		}
   890  		return true
   891  	})
   892  
   893  	n.runListeners(func(listener NotifyListener) {
   894  		listener.FavoritesChanged(uid)
   895  	})
   896  	n.G().Log.Debug("- Sent favorites changed notification")
   897  }
   898  
   899  func (n *NotifyRouter) HandleFSSubscriptionNotify(arg keybase1.FSSubscriptionNotifyArg) {
   900  	if n == nil {
   901  		return
   902  	}
   903  	// For all connections we currently have open...
   904  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   905  		// If the connection wants the `Kbfs` notification type
   906  		if n.getNotificationChannels(id).Kbfssubscription {
   907  			// In the background do...
   908  			go func() {
   909  				// A send of a `FSSyncActivity` RPC with the notification
   910  				_ = (keybase1.NotifyFSClient{
   911  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   912  				}).FSSubscriptionNotify(context.Background(), arg)
   913  			}()
   914  		}
   915  		return true
   916  	})
   917  	n.runListeners(func(listener NotifyListener) {
   918  		listener.FSSubscriptionNotify(arg)
   919  	})
   920  }
   921  
   922  func (n *NotifyRouter) HandleFSSubscriptionNotifyPath(arg keybase1.FSSubscriptionNotifyPathArg) {
   923  	if n == nil {
   924  		return
   925  	}
   926  	// For all connections we currently have open...
   927  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   928  		// If the connection wants the `Kbfs` notification type
   929  		if n.getNotificationChannels(id).Kbfssubscription {
   930  			// In the background do...
   931  			go func() {
   932  				// A send of a `FSSyncActivity` RPC with the notification
   933  				_ = (keybase1.NotifyFSClient{
   934  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   935  				}).FSSubscriptionNotifyPath(context.Background(), arg)
   936  			}()
   937  		}
   938  		return true
   939  	})
   940  	n.runListeners(func(listener NotifyListener) {
   941  		listener.FSSubscriptionNotifyPath(arg)
   942  	})
   943  }
   944  
   945  func (n *NotifyRouter) HandleSimpleFSArchiveStatusChanged(ctx context.Context, status keybase1.SimpleFSArchiveStatus) {
   946  	if n == nil {
   947  		return
   948  	}
   949  	// For all connections we currently have open...
   950  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   951  		// If the connection wants the `Kbfs` notification type
   952  		if n.getNotificationChannels(id).Notifysimplefs { // In the background do...
   953  			go func() {
   954  				_ = (keybase1.NotifySimpleFSClient{
   955  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   956  				}).SimpleFSArchiveStatusChanged(context.Background(), status)
   957  			}()
   958  		}
   959  		return true
   960  	})
   961  	n.runListeners(func(listener NotifyListener) {
   962  		listener.SimpleFSArchiveStatusChanged(status)
   963  	})
   964  }
   965  
   966  // HandleDeviceCloneNotification is called when a run of the device clone status update
   967  // finds a newly-added, possible clone. It will broadcast the messages to all curious listeners.
   968  func (n *NotifyRouter) HandleDeviceCloneNotification(newClones int) {
   969  	if n == nil {
   970  		return
   971  	}
   972  
   973  	n.G().Log.Debug("+ Sending device clone notification")
   974  	// For all connections we currently have open...
   975  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
   976  		// If the connection wants the `Deviceclone` notification type
   977  		if n.getNotificationChannels(id).Deviceclone {
   978  			// In the background do...
   979  			go func() {
   980  				// A send of a `DeviceCloneCountChanged` RPC with the number of newly discovered clones
   981  				_ = (keybase1.NotifyDeviceCloneClient{
   982  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
   983  				}).DeviceCloneCountChanged(context.Background(), newClones)
   984  			}()
   985  		}
   986  		return true
   987  	})
   988  
   989  	n.runListeners(func(listener NotifyListener) {
   990  		listener.DeviceCloneCountChanged(newClones)
   991  	})
   992  	n.G().Log.Debug("- Sent device clone notification")
   993  }
   994  
   995  // canSkipNotification checks if the notification can be ignored and the given
   996  // connection allows skips.
   997  func (n *NotifyRouter) canSkipNotif(id ConnectionID, canSkip bool) bool {
   998  	return canSkip && n.getNotificationChannels(id).AllowChatNotifySkips
   999  }
  1000  
  1001  func (n *NotifyRouter) shouldSendChatNotification(id ConnectionID, topicType chat1.TopicType) bool {
  1002  	switch topicType {
  1003  	case chat1.TopicType_CHAT:
  1004  		return n.getNotificationChannels(id).Chat
  1005  	case chat1.TopicType_DEV:
  1006  		return n.getNotificationChannels(id).Chatdev
  1007  	case chat1.TopicType_KBFSFILEEDIT:
  1008  		return n.getNotificationChannels(id).Chatkbfsedits
  1009  	case chat1.TopicType_EMOJI:
  1010  		return n.getNotificationChannels(id).Chatemoji
  1011  	case chat1.TopicType_EMOJICROSS:
  1012  		return n.getNotificationChannels(id).Chatemojicross
  1013  	case chat1.TopicType_NONE:
  1014  		return n.getNotificationChannels(id).Chat ||
  1015  			n.getNotificationChannels(id).Chatdev ||
  1016  			n.getNotificationChannels(id).Chatkbfsedits ||
  1017  			n.getNotificationChannels(id).Chatemoji ||
  1018  			n.getNotificationChannels(id).Chatemojicross
  1019  	}
  1020  	return false
  1021  }
  1022  
  1023  func (n *NotifyRouter) HandleNewChatActivity(ctx context.Context, uid keybase1.UID,
  1024  	topicType chat1.TopicType, activity *chat1.ChatActivity, source chat1.ChatActivitySource, canSkip bool) {
  1025  	if n == nil {
  1026  		return
  1027  	}
  1028  	var wg sync.WaitGroup
  1029  	n.G().Log.CDebugf(ctx, "+ Sending NewChatActivity %v notification", source)
  1030  	// For all connections we currently have open...
  1031  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1032  		// If the connection wants the `Chat` notification type
  1033  		if n.shouldSendChatNotification(id, topicType) && !n.canSkipNotif(id, canSkip) {
  1034  			wg.Add(1)
  1035  			// In the background do...
  1036  			go func() {
  1037  				// A send of a `NewChatActivity` RPC with the user's UID
  1038  				_ = (chat1.NotifyChatClient{
  1039  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1040  				}).NewChatActivity(context.Background(), chat1.NewChatActivityArg{
  1041  					Uid:      uid,
  1042  					Activity: *activity,
  1043  					Source:   source,
  1044  				})
  1045  				wg.Done()
  1046  			}()
  1047  		}
  1048  		return true
  1049  	})
  1050  	wg.Wait()
  1051  
  1052  	n.runListeners(func(listener NotifyListener) {
  1053  		listener.NewChatActivity(uid, *activity, source)
  1054  	})
  1055  	n.G().Log.CDebugf(ctx, "- Sent NewChatActivity notification")
  1056  }
  1057  
  1058  func (n *NotifyRouter) HandleChatIdentifyUpdate(ctx context.Context, update keybase1.CanonicalTLFNameAndIDWithBreaks) {
  1059  	if n == nil {
  1060  		return
  1061  	}
  1062  	var wg sync.WaitGroup
  1063  	n.G().Log.CDebugf(ctx, "+ Sending ChatIdentifyUpdate notification")
  1064  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1065  		if n.shouldSendChatNotification(id, chat1.TopicType_CHAT) {
  1066  			wg.Add(1)
  1067  			go func() {
  1068  				_ = (chat1.NotifyChatClient{
  1069  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1070  				}).ChatIdentifyUpdate(context.Background(), update)
  1071  				wg.Done()
  1072  			}()
  1073  		}
  1074  		return true
  1075  	})
  1076  	wg.Wait()
  1077  
  1078  	n.runListeners(func(listener NotifyListener) {
  1079  		listener.ChatIdentifyUpdate(update)
  1080  	})
  1081  	n.G().Log.CDebugf(ctx, "- Sent ChatIdentifyUpdate notification")
  1082  }
  1083  
  1084  func (n *NotifyRouter) HandleChatTLFFinalize(ctx context.Context, uid keybase1.UID,
  1085  	convID chat1.ConversationID, topicType chat1.TopicType, finalizeInfo chat1.ConversationFinalizeInfo,
  1086  	conv *chat1.InboxUIItem) {
  1087  	if n == nil {
  1088  		return
  1089  	}
  1090  	var wg sync.WaitGroup
  1091  	n.G().Log.CDebugf(ctx, "+ Sending ChatTLFFinalize notification")
  1092  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1093  		if n.shouldSendChatNotification(id, topicType) {
  1094  			wg.Add(1)
  1095  			go func() {
  1096  				_ = (chat1.NotifyChatClient{
  1097  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1098  				}).ChatTLFFinalize(context.Background(), chat1.ChatTLFFinalizeArg{
  1099  					Uid:          uid,
  1100  					ConvID:       convID,
  1101  					FinalizeInfo: finalizeInfo,
  1102  					Conv:         conv,
  1103  				})
  1104  				wg.Done()
  1105  			}()
  1106  		}
  1107  		return true
  1108  	})
  1109  	wg.Wait()
  1110  
  1111  	n.runListeners(func(listener NotifyListener) {
  1112  		listener.ChatTLFFinalize(uid, convID, finalizeInfo)
  1113  	})
  1114  	n.G().Log.CDebugf(ctx, "- Sent ChatTLFFinalize notification")
  1115  }
  1116  
  1117  func (n *NotifyRouter) HandleChatTLFResolve(ctx context.Context, uid keybase1.UID,
  1118  	convID chat1.ConversationID, topicType chat1.TopicType, resolveInfo chat1.ConversationResolveInfo) {
  1119  	if n == nil {
  1120  		return
  1121  	}
  1122  	var wg sync.WaitGroup
  1123  	n.G().Log.CDebugf(ctx, "+ Sending ChatTLFResolve notification")
  1124  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1125  		if n.shouldSendChatNotification(id, topicType) {
  1126  			wg.Add(1)
  1127  			go func() {
  1128  				_ = (chat1.NotifyChatClient{
  1129  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1130  				}).ChatTLFResolve(context.Background(), chat1.ChatTLFResolveArg{
  1131  					Uid:         uid,
  1132  					ConvID:      convID,
  1133  					ResolveInfo: resolveInfo,
  1134  				})
  1135  				wg.Done()
  1136  			}()
  1137  		}
  1138  		return true
  1139  	})
  1140  	wg.Wait()
  1141  
  1142  	n.runListeners(func(listener NotifyListener) {
  1143  		listener.ChatTLFResolve(uid, convID, resolveInfo)
  1144  	})
  1145  	n.G().Log.CDebugf(ctx, "- Sent ChatTLFResolve notification")
  1146  }
  1147  
  1148  func (n *NotifyRouter) HandleChatInboxStale(ctx context.Context, uid keybase1.UID) {
  1149  	if n == nil {
  1150  		return
  1151  	}
  1152  	var wg sync.WaitGroup
  1153  	n.G().Log.CDebugf(ctx, "+ Sending ChatInboxStale notification")
  1154  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1155  		if n.shouldSendChatNotification(id, chat1.TopicType_NONE) {
  1156  			wg.Add(1)
  1157  			go func() {
  1158  				_ = (chat1.NotifyChatClient{
  1159  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1160  				}).ChatInboxStale(context.Background(), uid)
  1161  				wg.Done()
  1162  			}()
  1163  		}
  1164  		return true
  1165  	})
  1166  	wg.Wait()
  1167  
  1168  	n.runListeners(func(listener NotifyListener) {
  1169  		listener.ChatInboxStale(uid)
  1170  	})
  1171  	n.G().Log.CDebugf(ctx, "- Sent ChatInboxStale notification")
  1172  }
  1173  
  1174  func (n *NotifyRouter) HandleChatThreadsStale(ctx context.Context, uid keybase1.UID,
  1175  	updates []chat1.ConversationStaleUpdate) {
  1176  	if n == nil {
  1177  		return
  1178  	}
  1179  	var wg sync.WaitGroup
  1180  	n.G().Log.CDebugf(ctx, "+ Sending ChatThreadsStale notification")
  1181  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1182  		if n.shouldSendChatNotification(id, chat1.TopicType_NONE) {
  1183  			wg.Add(1)
  1184  			go func() {
  1185  				_ = (chat1.NotifyChatClient{
  1186  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1187  				}).ChatThreadsStale(context.Background(), chat1.ChatThreadsStaleArg{
  1188  					Uid:     uid,
  1189  					Updates: updates,
  1190  				})
  1191  				wg.Done()
  1192  			}()
  1193  		}
  1194  		return true
  1195  	})
  1196  	wg.Wait()
  1197  
  1198  	n.runListeners(func(listener NotifyListener) {
  1199  		listener.ChatThreadsStale(uid, updates)
  1200  	})
  1201  	n.G().Log.CDebugf(ctx, "- Sent ChatThreadsStale notification")
  1202  }
  1203  
  1204  func (n *NotifyRouter) HandleChatInboxSynced(ctx context.Context, uid keybase1.UID,
  1205  	topicType chat1.TopicType, syncRes chat1.ChatSyncResult) {
  1206  	if n == nil {
  1207  		return
  1208  	}
  1209  	var wg sync.WaitGroup
  1210  	typ, _ := syncRes.SyncType()
  1211  	n.G().Log.CDebugf(ctx, "+ Sending ChatInboxSynced notification: syncTyp: %v topicType: %v", typ, topicType)
  1212  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1213  		if n.shouldSendChatNotification(id, topicType) {
  1214  			wg.Add(1)
  1215  			go func() {
  1216  				_ = (chat1.NotifyChatClient{
  1217  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1218  				}).ChatInboxSynced(context.Background(), chat1.ChatInboxSyncedArg{
  1219  					Uid:     uid,
  1220  					SyncRes: syncRes,
  1221  				})
  1222  				wg.Done()
  1223  			}()
  1224  		}
  1225  		return true
  1226  	})
  1227  	wg.Wait()
  1228  
  1229  	n.runListeners(func(listener NotifyListener) {
  1230  		listener.ChatInboxSynced(uid, topicType, syncRes)
  1231  	})
  1232  	n.G().Log.CDebugf(ctx, "- Sent ChatInboxSynced notification")
  1233  }
  1234  
  1235  func (n *NotifyRouter) HandleChatInboxSyncStarted(ctx context.Context, uid keybase1.UID) {
  1236  	if n == nil {
  1237  		return
  1238  	}
  1239  	var wg sync.WaitGroup
  1240  	n.G().Log.CDebugf(ctx, "+ Sending ChatInboxSyncStarted notification")
  1241  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1242  		if n.shouldSendChatNotification(id, chat1.TopicType_NONE) {
  1243  			wg.Add(1)
  1244  			go func() {
  1245  				_ = (chat1.NotifyChatClient{
  1246  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1247  				}).ChatInboxSyncStarted(context.Background(), uid)
  1248  				wg.Done()
  1249  			}()
  1250  		}
  1251  		return true
  1252  	})
  1253  	wg.Wait()
  1254  
  1255  	n.runListeners(func(listener NotifyListener) {
  1256  		listener.ChatInboxSyncStarted(uid)
  1257  	})
  1258  	n.G().Log.CDebugf(ctx, "- Sent ChatInboxSyncStarted notification")
  1259  }
  1260  
  1261  func (n *NotifyRouter) HandleChatTypingUpdate(ctx context.Context, updates []chat1.ConvTypingUpdate) {
  1262  	if n == nil {
  1263  		return
  1264  	}
  1265  	var wg sync.WaitGroup
  1266  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1267  		if n.shouldSendChatNotification(id, chat1.TopicType_CHAT) {
  1268  			wg.Add(1)
  1269  			go func() {
  1270  				_ = (chat1.NotifyChatClient{
  1271  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1272  				}).ChatTypingUpdate(context.Background(), updates)
  1273  				wg.Done()
  1274  			}()
  1275  		}
  1276  		return true
  1277  	})
  1278  	wg.Wait()
  1279  
  1280  	n.runListeners(func(listener NotifyListener) {
  1281  		listener.ChatTypingUpdate(updates)
  1282  	})
  1283  }
  1284  
  1285  func (n *NotifyRouter) HandleChatJoinedConversation(ctx context.Context, uid keybase1.UID,
  1286  	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
  1287  	if n == nil {
  1288  		return
  1289  	}
  1290  	var wg sync.WaitGroup
  1291  	n.G().Log.CDebugf(ctx, "+ Sending ChatJoinedConversation notification")
  1292  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1293  		if n.shouldSendChatNotification(id, topicType) {
  1294  			wg.Add(1)
  1295  			go func() {
  1296  				_ = (chat1.NotifyChatClient{
  1297  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1298  				}).ChatJoinedConversation(context.Background(), chat1.ChatJoinedConversationArg{
  1299  					Uid:    uid,
  1300  					ConvID: convID,
  1301  					Conv:   conv,
  1302  				})
  1303  				wg.Done()
  1304  			}()
  1305  		}
  1306  		return true
  1307  	})
  1308  	wg.Wait()
  1309  
  1310  	n.runListeners(func(listener NotifyListener) {
  1311  		listener.ChatJoinedConversation(uid, convID, conv)
  1312  	})
  1313  	n.G().Log.CDebugf(ctx, "- Sent ChatJoinedConversation notification")
  1314  }
  1315  
  1316  func (n *NotifyRouter) HandleChatLeftConversation(ctx context.Context, uid keybase1.UID,
  1317  	convID chat1.ConversationID, topicType chat1.TopicType) {
  1318  	if n == nil {
  1319  		return
  1320  	}
  1321  	var wg sync.WaitGroup
  1322  	n.G().Log.CDebugf(ctx, "+ Sending ChatLeftConversation notification")
  1323  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1324  		if n.shouldSendChatNotification(id, topicType) {
  1325  			wg.Add(1)
  1326  			go func() {
  1327  				_ = (chat1.NotifyChatClient{
  1328  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1329  				}).ChatLeftConversation(context.Background(), chat1.ChatLeftConversationArg{
  1330  					Uid:    uid,
  1331  					ConvID: convID,
  1332  				})
  1333  				wg.Done()
  1334  			}()
  1335  		}
  1336  		return true
  1337  	})
  1338  	wg.Wait()
  1339  	n.runListeners(func(listener NotifyListener) {
  1340  		listener.ChatLeftConversation(uid, convID)
  1341  	})
  1342  	n.G().Log.CDebugf(ctx, "- Sent ChatLeftConversation notification")
  1343  }
  1344  
  1345  func (n *NotifyRouter) HandleChatResetConversation(ctx context.Context, uid keybase1.UID,
  1346  	convID chat1.ConversationID, topicType chat1.TopicType) {
  1347  	if n == nil {
  1348  		return
  1349  	}
  1350  	var wg sync.WaitGroup
  1351  	n.G().Log.CDebugf(ctx, "+ Sending ChatResetConversation notification")
  1352  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1353  		if n.shouldSendChatNotification(id, topicType) {
  1354  			wg.Add(1)
  1355  			go func() {
  1356  				_ = (chat1.NotifyChatClient{
  1357  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1358  				}).ChatResetConversation(context.Background(), chat1.ChatResetConversationArg{
  1359  					Uid:    uid,
  1360  					ConvID: convID,
  1361  				})
  1362  				wg.Done()
  1363  			}()
  1364  		}
  1365  		return true
  1366  	})
  1367  	wg.Wait()
  1368  
  1369  	n.runListeners(func(listener NotifyListener) {
  1370  		listener.ChatResetConversation(uid, convID)
  1371  	})
  1372  	n.G().Log.CDebugf(ctx, "- Sent ChatResetConversation notification")
  1373  }
  1374  
  1375  func (n *NotifyRouter) HandleChatKBFSToImpteamUpgrade(ctx context.Context, uid keybase1.UID,
  1376  	convID chat1.ConversationID, topicType chat1.TopicType) {
  1377  	if n == nil {
  1378  		return
  1379  	}
  1380  	var wg sync.WaitGroup
  1381  	n.G().Log.CDebugf(ctx, "+ Sending ChatKBFSToImpteamUpgrade notification")
  1382  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1383  		if n.shouldSendChatNotification(id, topicType) {
  1384  			wg.Add(1)
  1385  			go func() {
  1386  				_ = (chat1.NotifyChatClient{
  1387  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1388  				}).ChatKBFSToImpteamUpgrade(context.Background(), chat1.ChatKBFSToImpteamUpgradeArg{
  1389  					Uid:    uid,
  1390  					ConvID: convID,
  1391  				})
  1392  				wg.Done()
  1393  			}()
  1394  		}
  1395  		return true
  1396  	})
  1397  	wg.Wait()
  1398  
  1399  	n.runListeners(func(listener NotifyListener) {
  1400  		listener.ChatKBFSToImpteamUpgrade(uid, convID)
  1401  	})
  1402  	n.G().Log.CDebugf(ctx, "- Sent ChatKBFSToImpteamUpgrade notification")
  1403  }
  1404  
  1405  func (n *NotifyRouter) HandleChatAttachmentUploadStart(ctx context.Context, uid keybase1.UID,
  1406  	convID chat1.ConversationID, outboxID chat1.OutboxID) {
  1407  	if n == nil {
  1408  		return
  1409  	}
  1410  	var wg sync.WaitGroup
  1411  	n.G().Log.CDebugf(ctx, "+ Sending ChatAttachmentUploadStart notification")
  1412  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1413  		if n.getNotificationChannels(id).Chatattachments {
  1414  			wg.Add(1)
  1415  			go func() {
  1416  				_ = (chat1.NotifyChatClient{
  1417  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1418  				}).ChatAttachmentUploadStart(context.Background(), chat1.ChatAttachmentUploadStartArg{
  1419  					Uid:      uid,
  1420  					ConvID:   convID,
  1421  					OutboxID: outboxID,
  1422  				})
  1423  				wg.Done()
  1424  			}()
  1425  		}
  1426  		return true
  1427  	})
  1428  	wg.Wait()
  1429  
  1430  	n.runListeners(func(listener NotifyListener) {
  1431  		listener.ChatAttachmentUploadStart(uid, convID, outboxID)
  1432  	})
  1433  	n.G().Log.CDebugf(ctx, "- Sent ChatAttachmentUploadStart notification")
  1434  }
  1435  
  1436  func (n *NotifyRouter) HandleChatAttachmentUploadProgress(ctx context.Context, uid keybase1.UID,
  1437  	convID chat1.ConversationID, outboxID chat1.OutboxID, bytesComplete, bytesTotal int64) {
  1438  	if n == nil {
  1439  		return
  1440  	}
  1441  	var wg sync.WaitGroup
  1442  	n.G().Log.CDebugf(ctx, "+ Sending ChatAttachmentUploadProgress notification")
  1443  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1444  		if n.getNotificationChannels(id).Chatattachments {
  1445  			wg.Add(1)
  1446  			go func() {
  1447  				_ = (chat1.NotifyChatClient{
  1448  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1449  				}).ChatAttachmentUploadProgress(context.Background(), chat1.ChatAttachmentUploadProgressArg{
  1450  					Uid:           uid,
  1451  					ConvID:        convID,
  1452  					OutboxID:      outboxID,
  1453  					BytesComplete: bytesComplete,
  1454  					BytesTotal:    bytesTotal,
  1455  				})
  1456  				wg.Done()
  1457  			}()
  1458  		}
  1459  		return true
  1460  	})
  1461  	wg.Wait()
  1462  
  1463  	n.runListeners(func(listener NotifyListener) {
  1464  		listener.ChatAttachmentUploadProgress(uid, convID, outboxID, bytesComplete, bytesTotal)
  1465  	})
  1466  	n.G().Log.CDebugf(ctx, "- Sent ChatAttachmentUploadProgress notification")
  1467  }
  1468  
  1469  func (n *NotifyRouter) HandleChatAttachmentDownloadProgress(ctx context.Context, uid keybase1.UID,
  1470  	convID chat1.ConversationID, msgID chat1.MessageID, bytesComplete, bytesTotal int64) {
  1471  	if n == nil {
  1472  		return
  1473  	}
  1474  	var wg sync.WaitGroup
  1475  	n.G().Log.CDebugf(ctx, "+ Sending ChatAttachmentDownloadProgress notification")
  1476  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1477  		if n.getNotificationChannels(id).Chatattachments {
  1478  			wg.Add(1)
  1479  			go func() {
  1480  				_ = (chat1.NotifyChatClient{
  1481  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1482  				}).ChatAttachmentDownloadProgress(context.Background(), chat1.ChatAttachmentDownloadProgressArg{
  1483  					Uid:           uid,
  1484  					ConvID:        convID,
  1485  					MsgID:         msgID,
  1486  					BytesComplete: bytesComplete,
  1487  					BytesTotal:    bytesTotal,
  1488  				})
  1489  				wg.Done()
  1490  			}()
  1491  		}
  1492  		return true
  1493  	})
  1494  	wg.Wait()
  1495  
  1496  	n.runListeners(func(listener NotifyListener) {
  1497  		listener.ChatAttachmentDownloadProgress(uid, convID, msgID, bytesComplete, bytesTotal)
  1498  	})
  1499  	n.G().Log.CDebugf(ctx, "- Sent ChatAttachmentDownloadProgress notification")
  1500  }
  1501  
  1502  func (n *NotifyRouter) HandleChatAttachmentDownloadComplete(ctx context.Context, uid keybase1.UID,
  1503  	convID chat1.ConversationID, msgID chat1.MessageID) {
  1504  	if n == nil {
  1505  		return
  1506  	}
  1507  	var wg sync.WaitGroup
  1508  	n.G().Log.CDebugf(ctx, "+ Sending ChatAttachmentDownloadComplete notification")
  1509  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1510  		if n.getNotificationChannels(id).Chatattachments {
  1511  			wg.Add(1)
  1512  			go func() {
  1513  				_ = (chat1.NotifyChatClient{
  1514  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1515  				}).ChatAttachmentDownloadComplete(context.Background(), chat1.ChatAttachmentDownloadCompleteArg{
  1516  					Uid:    uid,
  1517  					ConvID: convID,
  1518  					MsgID:  msgID,
  1519  				})
  1520  				wg.Done()
  1521  			}()
  1522  		}
  1523  		return true
  1524  	})
  1525  	wg.Wait()
  1526  
  1527  	n.runListeners(func(listener NotifyListener) {
  1528  		listener.ChatAttachmentDownloadComplete(uid, convID, msgID)
  1529  	})
  1530  	n.G().Log.CDebugf(ctx, "- Sent ChatAttachmentDownloadComplete notification")
  1531  }
  1532  
  1533  func (n *NotifyRouter) HandleChatArchiveProgress(ctx context.Context, jobID chat1.ArchiveJobID, messagesComplete, messagesTotal int64) {
  1534  	if n == nil {
  1535  		return
  1536  	}
  1537  	var wg sync.WaitGroup
  1538  	n.G().Log.CDebugf(ctx, "+ Sending ChatArchiveProgress notification")
  1539  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1540  		if n.getNotificationChannels(id).Chatarchive {
  1541  			wg.Add(1)
  1542  			go func() {
  1543  				_ = (chat1.NotifyChatClient{
  1544  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1545  				}).ChatArchiveProgress(context.Background(), chat1.ChatArchiveProgressArg{
  1546  					JobID:            jobID,
  1547  					MessagesComplete: messagesComplete,
  1548  					MessagesTotal:    messagesTotal,
  1549  				})
  1550  				wg.Done()
  1551  			}()
  1552  		}
  1553  		return true
  1554  	})
  1555  	wg.Wait()
  1556  
  1557  	n.runListeners(func(listener NotifyListener) {
  1558  		listener.ChatArchiveProgress(jobID, messagesComplete, messagesTotal)
  1559  	})
  1560  	n.G().Log.CDebugf(ctx, "- Sent ChatArchiveProgress notification")
  1561  }
  1562  
  1563  func (n *NotifyRouter) HandleChatArchiveComplete(ctx context.Context, jobID chat1.ArchiveJobID) {
  1564  	if n == nil {
  1565  		return
  1566  	}
  1567  	var wg sync.WaitGroup
  1568  	n.G().Log.CDebugf(ctx, "+ Sending ChatArchiveComplete notification")
  1569  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1570  		if n.getNotificationChannels(id).Chatarchive {
  1571  			wg.Add(1)
  1572  			go func() {
  1573  				_ = (chat1.NotifyChatClient{
  1574  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1575  				}).ChatArchiveComplete(context.Background(),
  1576  					jobID,
  1577  				)
  1578  				wg.Done()
  1579  			}()
  1580  		}
  1581  		return true
  1582  	})
  1583  	wg.Wait()
  1584  
  1585  	n.runListeners(func(listener NotifyListener) {
  1586  		listener.ChatArchiveComplete(jobID)
  1587  	})
  1588  	n.G().Log.CDebugf(ctx, "- Sent ChatArchiveComplete notification")
  1589  }
  1590  
  1591  func (n *NotifyRouter) HandleChatSetConvRetention(ctx context.Context, uid keybase1.UID,
  1592  	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
  1593  	n.notifyChatCommon(ctx, "ChatSetConvRetention", topicType,
  1594  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1595  			_ = cli.ChatSetConvRetention(ctx, chat1.ChatSetConvRetentionArg{
  1596  				Uid:    uid,
  1597  				ConvID: convID,
  1598  				Conv:   conv,
  1599  			})
  1600  		}, func(ctx context.Context, listener NotifyListener) {
  1601  			listener.ChatSetConvRetention(uid, convID)
  1602  		})
  1603  }
  1604  
  1605  func (n *NotifyRouter) HandleChatSetTeamRetention(ctx context.Context, uid keybase1.UID,
  1606  	teamID keybase1.TeamID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
  1607  	n.notifyChatCommon(ctx, "ChatSetTeamRetention", topicType,
  1608  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1609  			_ = cli.ChatSetTeamRetention(ctx, chat1.ChatSetTeamRetentionArg{
  1610  				Uid:    uid,
  1611  				TeamID: teamID,
  1612  				Convs:  convs,
  1613  			})
  1614  		}, func(ctx context.Context, listener NotifyListener) {
  1615  			listener.ChatSetTeamRetention(uid, teamID)
  1616  		})
  1617  }
  1618  
  1619  func (n *NotifyRouter) HandleChatSetConvSettings(ctx context.Context, uid keybase1.UID,
  1620  	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
  1621  	n.notifyChatCommon(ctx, "ChatSetConvSettings", topicType,
  1622  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1623  			_ = cli.ChatSetConvSettings(ctx, chat1.ChatSetConvSettingsArg{
  1624  				Uid:    uid,
  1625  				ConvID: convID,
  1626  				Conv:   conv,
  1627  			})
  1628  		}, func(ctx context.Context, listener NotifyListener) {
  1629  			listener.ChatSetConvSettings(uid, convID)
  1630  		})
  1631  }
  1632  
  1633  func (n *NotifyRouter) HandleChatSubteamRename(ctx context.Context, uid keybase1.UID,
  1634  	convIDs []chat1.ConversationID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
  1635  	n.notifyChatCommon(ctx, "ChatSubteamRename", topicType,
  1636  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1637  			_ = cli.ChatSubteamRename(ctx, chat1.ChatSubteamRenameArg{
  1638  				Uid:   uid,
  1639  				Convs: convs,
  1640  			})
  1641  		}, func(ctx context.Context, listener NotifyListener) {
  1642  			listener.ChatSubteamRename(uid, convIDs)
  1643  		})
  1644  }
  1645  
  1646  func (n *NotifyRouter) HandleChatPromptUnfurl(ctx context.Context, uid keybase1.UID,
  1647  	convID chat1.ConversationID, msgID chat1.MessageID, domain string) {
  1648  	n.notifyChatCommon(ctx, "ChatPromptUnfurl", chat1.TopicType_CHAT,
  1649  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1650  			_ = cli.ChatPromptUnfurl(ctx, chat1.ChatPromptUnfurlArg{
  1651  				Uid:    uid,
  1652  				ConvID: convID,
  1653  				MsgID:  msgID,
  1654  				Domain: domain,
  1655  			})
  1656  		}, func(ctx context.Context, listener NotifyListener) {
  1657  			listener.ChatPromptUnfurl(uid, convID, msgID, domain)
  1658  		})
  1659  }
  1660  
  1661  func (n *NotifyRouter) HandleChatConvUpdate(ctx context.Context, uid keybase1.UID,
  1662  	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
  1663  	n.notifyChatCommon(ctx, "ChatConvUpdate", topicType,
  1664  		func(ctx context.Context, cli *chat1.NotifyChatClient) {
  1665  			_ = cli.ChatConvUpdate(ctx, chat1.ChatConvUpdateArg{
  1666  				Uid:    uid,
  1667  				ConvID: convID,
  1668  				Conv:   conv,
  1669  			})
  1670  		}, func(ctx context.Context, listener NotifyListener) {
  1671  			listener.ChatConvUpdate(uid, convID)
  1672  		})
  1673  }
  1674  
  1675  func (n *NotifyRouter) HandleChatWelcomeMessageLoaded(ctx context.Context,
  1676  	teamID keybase1.TeamID, message chat1.WelcomeMessageDisplay) {
  1677  	if n == nil {
  1678  		return
  1679  	}
  1680  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1681  		if n.getNotificationChannels(id).Chat {
  1682  			go func() {
  1683  				_ = (chat1.NotifyChatClient{
  1684  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1685  				}).ChatWelcomeMessageLoaded(ctx, chat1.ChatWelcomeMessageLoadedArg{
  1686  					TeamID:  teamID,
  1687  					Message: message,
  1688  				})
  1689  			}()
  1690  		}
  1691  		return true
  1692  	})
  1693  
  1694  	n.runListeners(func(listener NotifyListener) {
  1695  		listener.ChatWelcomeMessageLoaded(teamID, message)
  1696  	})
  1697  }
  1698  
  1699  func (n *NotifyRouter) HandleChatParticipantsInfo(ctx context.Context,
  1700  	participants map[chat1.ConvIDStr][]chat1.UIParticipant) {
  1701  	if n == nil {
  1702  		return
  1703  	}
  1704  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1705  		if n.getNotificationChannels(id).Chat {
  1706  			go func() {
  1707  				_ = (chat1.NotifyChatClient{
  1708  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1709  				}).ChatParticipantsInfo(ctx, participants)
  1710  			}()
  1711  		}
  1712  		return true
  1713  	})
  1714  
  1715  	n.runListeners(func(listener NotifyListener) {
  1716  		listener.ChatParticipantsInfo(participants)
  1717  	})
  1718  }
  1719  
  1720  type notifyChatFn1 func(context.Context, *chat1.NotifyChatClient)
  1721  type notifyChatFn2 func(context.Context, NotifyListener)
  1722  
  1723  func (n *NotifyRouter) notifyChatCommon(ctx context.Context, debugLabel string, topicType chat1.TopicType,
  1724  	fn1 notifyChatFn1, fn2 notifyChatFn2) {
  1725  	if n == nil {
  1726  		return
  1727  	}
  1728  	var wg sync.WaitGroup
  1729  	n.G().Log.CDebugf(ctx, "+ Sending %v notification", debugLabel)
  1730  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1731  		if n.shouldSendChatNotification(id, topicType) {
  1732  			wg.Add(1)
  1733  			go func() {
  1734  				cli := &chat1.NotifyChatClient{
  1735  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1736  				}
  1737  				fn1(context.Background(), cli)
  1738  				wg.Done()
  1739  			}()
  1740  		}
  1741  		return true
  1742  	})
  1743  	wg.Wait()
  1744  
  1745  	n.runListeners(func(listener NotifyListener) {
  1746  		fn2(ctx, listener)
  1747  	})
  1748  	n.G().Log.CDebugf(ctx, "- Sent %v notification", debugLabel)
  1749  }
  1750  
  1751  func (n *NotifyRouter) HandleWalletPaymentNotification(ctx context.Context, accountID stellar1.AccountID, paymentID stellar1.PaymentID) {
  1752  	if n == nil {
  1753  		return
  1754  	}
  1755  	n.G().Log.CDebugf(ctx, "+ Sending wallet PaymentNotification")
  1756  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1757  		// If the connection wants the `Wallet` notification type
  1758  		if n.getNotificationChannels(id).Wallet {
  1759  			// In the background do...
  1760  			go func() {
  1761  				arg := stellar1.PaymentNotificationArg{
  1762  					AccountID: accountID,
  1763  					PaymentID: paymentID,
  1764  				}
  1765  				_ = (stellar1.NotifyClient{
  1766  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1767  				}).PaymentNotification(context.Background(), arg)
  1768  			}()
  1769  		}
  1770  		return true
  1771  	})
  1772  
  1773  	n.runListeners(func(listener NotifyListener) {
  1774  		listener.WalletPaymentNotification(accountID, paymentID)
  1775  	})
  1776  	n.G().Log.CDebugf(ctx, "- Sent wallet PaymentNotification")
  1777  }
  1778  
  1779  func (n *NotifyRouter) HandleWalletPaymentStatusNotification(ctx context.Context, accountID stellar1.AccountID, paymentID stellar1.PaymentID) {
  1780  	if n == nil {
  1781  		return
  1782  	}
  1783  	n.G().Log.CDebugf(ctx, "+ Sending wallet PaymentStatusNotification")
  1784  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1785  		// If the connection wants the `Wallet` notification type
  1786  		if n.getNotificationChannels(id).Wallet {
  1787  			// In the background do...
  1788  			go func() {
  1789  				arg := stellar1.PaymentStatusNotificationArg{
  1790  					AccountID: accountID,
  1791  					PaymentID: paymentID,
  1792  				}
  1793  				_ = (stellar1.NotifyClient{
  1794  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1795  				}).PaymentStatusNotification(context.Background(), arg)
  1796  			}()
  1797  		}
  1798  		return true
  1799  	})
  1800  
  1801  	n.runListeners(func(listener NotifyListener) {
  1802  		listener.WalletPaymentStatusNotification(accountID, paymentID)
  1803  	})
  1804  	n.G().Log.CDebugf(ctx, "- Sent wallet PaymentStatusNotification")
  1805  }
  1806  
  1807  func (n *NotifyRouter) HandleWalletRequestStatusNotification(ctx context.Context, reqID stellar1.KeybaseRequestID) {
  1808  	if n == nil {
  1809  		return
  1810  	}
  1811  	n.G().Log.CDebugf(ctx, "+ Sending wallet RequestStatusNotification")
  1812  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1813  		// If the connection wants the `Wallet` notification type
  1814  		if n.getNotificationChannels(id).Wallet {
  1815  			// In the background do...
  1816  			go func() {
  1817  				_ = (stellar1.NotifyClient{
  1818  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1819  				}).RequestStatusNotification(context.Background(), reqID)
  1820  			}()
  1821  		}
  1822  		return true
  1823  	})
  1824  
  1825  	n.runListeners(func(listener NotifyListener) {
  1826  		listener.WalletRequestStatusNotification(reqID)
  1827  	})
  1828  	n.G().Log.CDebugf(ctx, "- Sent wallet RequestStatusNotification")
  1829  }
  1830  
  1831  func (n *NotifyRouter) HandleWalletAccountDetailsUpdate(ctx context.Context, accountID stellar1.AccountID, account stellar1.WalletAccountLocal) {
  1832  	if n == nil {
  1833  		return
  1834  	}
  1835  	n.G().Log.CDebugf(ctx, "+ Sending wallet AccountDetailsUpdate")
  1836  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1837  		// If the connection wants the `Wallet` notification type
  1838  		if n.getNotificationChannels(id).Wallet {
  1839  			// In the background do...
  1840  			go func() {
  1841  				arg := stellar1.AccountDetailsUpdateArg{
  1842  					AccountID: accountID,
  1843  					Account:   account,
  1844  				}
  1845  				_ = (stellar1.NotifyClient{
  1846  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1847  				}).AccountDetailsUpdate(context.Background(), arg)
  1848  			}()
  1849  		}
  1850  		return true
  1851  	})
  1852  
  1853  	n.runListeners(func(listener NotifyListener) {
  1854  		listener.WalletAccountDetailsUpdate(accountID, account)
  1855  	})
  1856  	n.G().Log.CDebugf(ctx, "- Sent wallet AccountDetailsUpdate")
  1857  }
  1858  
  1859  func (n *NotifyRouter) HandleWalletAccountsUpdate(ctx context.Context, accounts []stellar1.WalletAccountLocal) {
  1860  	if n == nil {
  1861  		return
  1862  	}
  1863  	n.G().Log.CDebugf(ctx, "+ Sending wallet AccountsUpdate")
  1864  
  1865  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1866  		// If the connection wants the `Wallet` notification type
  1867  		if n.getNotificationChannels(id).Wallet {
  1868  			// In the background do...
  1869  			go func() {
  1870  				_ = (stellar1.NotifyClient{
  1871  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1872  				}).AccountsUpdate(context.Background(), accounts)
  1873  			}()
  1874  		}
  1875  		return true
  1876  	})
  1877  
  1878  	n.runListeners(func(listener NotifyListener) {
  1879  		listener.WalletAccountsUpdate(accounts)
  1880  	})
  1881  	n.G().Log.CDebugf(ctx, "- Sent wallet AccountsUpdate")
  1882  }
  1883  
  1884  func (n *NotifyRouter) HandleWalletPendingPaymentsUpdate(ctx context.Context, accountID stellar1.AccountID, pending []stellar1.PaymentOrErrorLocal) {
  1885  	if n == nil {
  1886  		return
  1887  	}
  1888  	n.G().Log.CDebugf(ctx, "+ Sending wallet PendingPaymentsUpdate")
  1889  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1890  		// If the connection wants the `Wallet` notification type
  1891  		if n.getNotificationChannels(id).Wallet {
  1892  			// In the background do...
  1893  			go func() {
  1894  				arg := stellar1.PendingPaymentsUpdateArg{
  1895  					AccountID: accountID,
  1896  					Pending:   pending,
  1897  				}
  1898  				_ = (stellar1.NotifyClient{
  1899  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1900  				}).PendingPaymentsUpdate(context.Background(), arg)
  1901  			}()
  1902  		}
  1903  		return true
  1904  	})
  1905  
  1906  	n.runListeners(func(listener NotifyListener) {
  1907  		listener.WalletPendingPaymentsUpdate(accountID, pending)
  1908  	})
  1909  	n.G().Log.CDebugf(ctx, "- Sent wallet PendingPaymentsUpdate")
  1910  }
  1911  
  1912  func (n *NotifyRouter) HandleWalletRecentPaymentsUpdate(ctx context.Context, accountID stellar1.AccountID, firstPage stellar1.PaymentsPageLocal) {
  1913  	if n == nil {
  1914  		return
  1915  	}
  1916  	n.G().Log.CDebugf(ctx, "+ Sending wallet RecentPaymentsUpdate")
  1917  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1918  		// If the connection wants the `Wallet` notification type
  1919  		if n.getNotificationChannels(id).Wallet {
  1920  			// In the background do...
  1921  			go func() {
  1922  				arg := stellar1.RecentPaymentsUpdateArg{
  1923  					AccountID: accountID,
  1924  					FirstPage: firstPage,
  1925  				}
  1926  				_ = (stellar1.NotifyClient{
  1927  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1928  				}).RecentPaymentsUpdate(context.Background(), arg)
  1929  			}()
  1930  		}
  1931  		return true
  1932  	})
  1933  
  1934  	n.runListeners(func(listener NotifyListener) {
  1935  		listener.WalletRecentPaymentsUpdate(accountID, firstPage)
  1936  	})
  1937  	n.G().Log.CDebugf(ctx, "- Sent wallet RecentPaymentsUpdate")
  1938  }
  1939  
  1940  // HandlePaperKeyCached is called whenever a paper key is cached
  1941  // in response to a rekey harassment.
  1942  func (n *NotifyRouter) HandlePaperKeyCached(uid keybase1.UID, encKID keybase1.KID, sigKID keybase1.KID) {
  1943  	if n == nil {
  1944  		return
  1945  	}
  1946  
  1947  	n.G().Log.Debug("+ Sending paperkey cached notification")
  1948  	arg := keybase1.PaperKeyCachedArg{
  1949  		Uid:    uid,
  1950  		EncKID: encKID,
  1951  		SigKID: sigKID,
  1952  	}
  1953  	var wg sync.WaitGroup
  1954  
  1955  	// For all connections we currently have open...
  1956  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1957  		// If the connection wants the `Favorites` notification type
  1958  		if n.getNotificationChannels(id).Paperkeys {
  1959  			wg.Add(1)
  1960  			// In the background do...
  1961  			go func() {
  1962  				_ = (keybase1.NotifyPaperKeyClient{
  1963  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1964  				}).PaperKeyCached(context.Background(), arg)
  1965  				wg.Done()
  1966  			}()
  1967  		}
  1968  		return true
  1969  	})
  1970  	wg.Wait()
  1971  
  1972  	n.runListeners(func(listener NotifyListener) {
  1973  		listener.PaperKeyCached(uid, encKID, sigKID)
  1974  	})
  1975  	n.G().Log.Debug("- Sent paperkey cached notification")
  1976  }
  1977  
  1978  // HandleKeyfamilyChanged is called whenever a user's keyfamily changes.
  1979  func (n *NotifyRouter) HandleKeyfamilyChanged(uid keybase1.UID) {
  1980  	if n == nil {
  1981  		return
  1982  	}
  1983  
  1984  	n.G().Log.Debug("+ Sending keyfamily changed notification")
  1985  	// For all connections we currently have open...
  1986  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  1987  		// If the connection wants the `Favorites` notification type
  1988  		if n.getNotificationChannels(id).Keyfamily {
  1989  			// In the background do...
  1990  			go func() {
  1991  				_ = (keybase1.NotifyKeyfamilyClient{
  1992  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  1993  				}).KeyfamilyChanged(context.Background(), uid)
  1994  			}()
  1995  		}
  1996  		return true
  1997  	})
  1998  
  1999  	n.runListeners(func(listener NotifyListener) {
  2000  		listener.KeyfamilyChanged(uid)
  2001  	})
  2002  	n.G().Log.Debug("- Sent keyfamily changed notification")
  2003  }
  2004  
  2005  // HandleServiceShutdown is called whenever the service shuts down.
  2006  func (n *NotifyRouter) HandleServiceShutdown() {
  2007  	if n == nil {
  2008  		return
  2009  	}
  2010  
  2011  	n.G().Log.Debug("+ Sending service shutdown notification")
  2012  
  2013  	var wg sync.WaitGroup
  2014  
  2015  	// For all connections we currently have open...
  2016  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2017  		// If the connection wants the `Service` notification type
  2018  		if n.getNotificationChannels(id).Service {
  2019  			// In the background do...
  2020  			wg.Add(1)
  2021  			go func() {
  2022  				_ = (keybase1.NotifyServiceClient{
  2023  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2024  				}).Shutdown(context.Background(), int(n.G().ExitCode))
  2025  				wg.Done()
  2026  			}()
  2027  		}
  2028  		return true
  2029  	})
  2030  
  2031  	done := make(chan struct{})
  2032  	go func() {
  2033  		wg.Wait()
  2034  		close(done)
  2035  	}()
  2036  
  2037  	// timeout after 4s (launchd will SIGKILL after 5s)
  2038  	select {
  2039  	case <-done:
  2040  		n.G().Log.Debug("Finished sending service shutdown notifications")
  2041  	case <-time.After(4 * time.Second):
  2042  		n.G().Log.Warning("Timed out sending service shutdown notifications, proceeding to shutdown")
  2043  	}
  2044  
  2045  	n.G().Log.Debug("- Sent service shutdown notification")
  2046  }
  2047  
  2048  // HandleAppExit is called whenever an app exit command is issued
  2049  func (n *NotifyRouter) HandleAppExit() {
  2050  	if n == nil {
  2051  		return
  2052  	}
  2053  	n.G().Log.Debug("+ Sending app exit notification")
  2054  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2055  		if n.getNotificationChannels(id).App {
  2056  			go func() {
  2057  				_ = (keybase1.NotifyAppClient{
  2058  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2059  				}).Exit(context.Background())
  2060  			}()
  2061  		}
  2062  		return true
  2063  	})
  2064  	n.G().Log.Debug("- Sent app exit notification")
  2065  }
  2066  
  2067  // HandlePGPKeyInSecretStoreFile is called to notify a user that they have a PGP
  2068  // key that is unlockable by a secret stored in a file in their home directory.
  2069  func (n *NotifyRouter) HandlePGPKeyInSecretStoreFile() {
  2070  	n.G().Log.Debug("+ Sending pgpKeyInSecretStoreFile notification")
  2071  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2072  		if n.getNotificationChannels(id).PGP {
  2073  			go func() {
  2074  				_ = (keybase1.NotifyPGPClient{
  2075  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2076  				}).PGPKeyInSecretStoreFile(context.Background())
  2077  			}()
  2078  		}
  2079  		return true
  2080  	})
  2081  
  2082  	n.runListeners(func(listener NotifyListener) {
  2083  		listener.PGPKeyInSecretStoreFile()
  2084  	})
  2085  	n.G().Log.Debug("- Sent pgpKeyInSecretStoreFile notification")
  2086  }
  2087  
  2088  func (n *NotifyRouter) HandleReachability(r keybase1.Reachability) {
  2089  	n.G().Log.Debug("+ Sending reachability")
  2090  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2091  		if n.getNotificationChannels(id).Reachability {
  2092  			go func() {
  2093  				_ = (keybase1.ReachabilityClient{
  2094  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2095  				}).ReachabilityChanged(context.Background(), r)
  2096  			}()
  2097  		}
  2098  		return true
  2099  	})
  2100  	n.runListeners(func(listener NotifyListener) {
  2101  		listener.Reachability(r)
  2102  	})
  2103  	n.G().Log.Debug("- Sent reachability")
  2104  }
  2105  
  2106  // teamID and teamName are not necessarily the same team
  2107  func (n *NotifyRouter) HandleTeamChangedByBothKeys(ctx context.Context,
  2108  	teamID keybase1.TeamID, teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet,
  2109  	latestHiddenSeqno keybase1.Seqno, latestOffchainSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
  2110  
  2111  	n.HandleTeamChangedByID(ctx, teamID, latestSeqno, implicitTeam, changes, latestHiddenSeqno, latestOffchainSeqno, source)
  2112  	n.HandleTeamChangedByName(ctx, teamName, latestSeqno, implicitTeam, changes, latestHiddenSeqno, latestOffchainSeqno, source)
  2113  }
  2114  
  2115  func (n *NotifyRouter) HandleTeamChangedByID(ctx context.Context,
  2116  	teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet,
  2117  	latestHiddenSeqno keybase1.Seqno, latestOffchainSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
  2118  
  2119  	if n == nil {
  2120  		return
  2121  	}
  2122  
  2123  	arg := keybase1.TeamChangedByIDArg{
  2124  		TeamID:              teamID,
  2125  		LatestSeqno:         latestSeqno,
  2126  		ImplicitTeam:        implicitTeam,
  2127  		Changes:             changes,
  2128  		LatestHiddenSeqno:   latestHiddenSeqno,
  2129  		LatestOffchainSeqno: latestOffchainSeqno,
  2130  		Source:              source,
  2131  	}
  2132  
  2133  	var wg sync.WaitGroup
  2134  	n.G().Log.CDebugf(ctx, "+ Sending TeamChangedByID notification (team:%v, seqno:%v, implicit:%v)",
  2135  		teamID, latestSeqno, implicitTeam)
  2136  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2137  		if n.getNotificationChannels(id).Team {
  2138  			wg.Add(1)
  2139  			go func() {
  2140  				_ = (keybase1.NotifyTeamClient{
  2141  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2142  				}).TeamChangedByID(context.Background(), arg)
  2143  				wg.Done()
  2144  			}()
  2145  		}
  2146  		return true
  2147  	})
  2148  	wg.Wait()
  2149  
  2150  	n.runListeners(func(listener NotifyListener) {
  2151  		listener.TeamChangedByID(teamID, latestSeqno, implicitTeam, changes, latestHiddenSeqno, source)
  2152  	})
  2153  	n.G().Log.CDebugf(ctx, "- Sent TeamChangedByID notification")
  2154  }
  2155  
  2156  func (n *NotifyRouter) HandleTeamChangedByName(ctx context.Context,
  2157  	teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet,
  2158  	latestHiddenSeqno keybase1.Seqno, latestOffchainSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
  2159  
  2160  	if n == nil {
  2161  		return
  2162  	}
  2163  
  2164  	arg := keybase1.TeamChangedByNameArg{
  2165  		TeamName:            teamName,
  2166  		LatestSeqno:         latestSeqno,
  2167  		ImplicitTeam:        implicitTeam,
  2168  		Changes:             changes,
  2169  		LatestHiddenSeqno:   latestHiddenSeqno,
  2170  		LatestOffchainSeqno: latestOffchainSeqno,
  2171  		Source:              source,
  2172  	}
  2173  
  2174  	var wg sync.WaitGroup
  2175  	n.G().Log.CDebugf(ctx, "+ Sending TeamChangedByName notification (team:%v, seqno:%v, implicit:%v)",
  2176  		teamName, latestSeqno, implicitTeam)
  2177  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2178  		if n.getNotificationChannels(id).Team {
  2179  			wg.Add(1)
  2180  			go func() {
  2181  				_ = (keybase1.NotifyTeamClient{
  2182  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2183  				}).TeamChangedByName(context.Background(), arg)
  2184  				wg.Done()
  2185  			}()
  2186  		}
  2187  		return true
  2188  	})
  2189  	wg.Wait()
  2190  
  2191  	n.runListeners(func(listener NotifyListener) {
  2192  		listener.TeamChangedByName(teamName, latestSeqno, implicitTeam, changes, latestHiddenSeqno, source)
  2193  	})
  2194  	n.G().Log.CDebugf(ctx, "- Sent TeamChanged notification")
  2195  }
  2196  
  2197  // TeamMetadataUpdateUnverified is called when a notification is received that
  2198  // affects the teams tab root page.
  2199  func (n *NotifyRouter) HandleTeamMetadataUpdate(ctx context.Context) {
  2200  	if n == nil {
  2201  		return
  2202  	}
  2203  
  2204  	n.G().Log.Debug("+ Sending TeamMetadataUpdate notification")
  2205  	// For all connections we currently have open...
  2206  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2207  		// If the connection wants the `Team` notifications
  2208  		if n.getNotificationChannels(id).Team {
  2209  			// In the background do...
  2210  			go func() {
  2211  				// A send of a `TeamListUnverifiedChanged` RPC
  2212  				_ = (keybase1.NotifyTeamClient{
  2213  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2214  				}).TeamMetadataUpdate(context.Background())
  2215  			}()
  2216  		}
  2217  		return true
  2218  	})
  2219  
  2220  	n.runListeners(func(listener NotifyListener) {
  2221  		listener.TeamMetadataUpdate()
  2222  	})
  2223  	n.G().Log.Debug("- Sent TeamMetadata notification")
  2224  }
  2225  
  2226  // HandleCanUserPerformChanged is called when a notification is received from gregor that
  2227  // we think might be of interest to the UI, specifically the parts of the UI that update
  2228  // permissions for a user in a team.
  2229  func (n *NotifyRouter) HandleCanUserPerformChanged(ctx context.Context, teamName string) {
  2230  	if n == nil {
  2231  		return
  2232  	}
  2233  
  2234  	n.G().Log.Debug("+ Sending CanUserPerformChanged notification (team:%v)", teamName)
  2235  	// For all connections we currently have open...
  2236  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2237  		// If the connection wants the `Team` notification type
  2238  		if n.getNotificationChannels(id).Team {
  2239  			// In the background do...
  2240  			go func() {
  2241  				// A send of a `CanUserPerformChanged` RPC
  2242  				_ = (keybase1.NotifyCanUserPerformClient{
  2243  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2244  				}).CanUserPerformChanged(context.Background(), teamName)
  2245  			}()
  2246  		}
  2247  		return true
  2248  	})
  2249  
  2250  	n.runListeners(func(listener NotifyListener) {
  2251  		listener.CanUserPerformChanged(teamName)
  2252  	})
  2253  	n.G().Log.Debug("- Sent CanUserPerformChanged notification (team:%v)", teamName)
  2254  }
  2255  
  2256  func (n *NotifyRouter) HandleTeamDeleted(ctx context.Context, teamID keybase1.TeamID) {
  2257  	if n == nil {
  2258  		return
  2259  	}
  2260  
  2261  	var wg sync.WaitGroup
  2262  	n.G().Log.CDebugf(ctx, "+ Sending TeamDeleted notification")
  2263  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2264  		if n.getNotificationChannels(id).Team {
  2265  			wg.Add(1)
  2266  			go func() {
  2267  				_ = (keybase1.NotifyTeamClient{
  2268  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2269  				}).TeamDeleted(context.Background(), teamID)
  2270  				wg.Done()
  2271  			}()
  2272  		}
  2273  		return true
  2274  	})
  2275  	wg.Wait()
  2276  
  2277  	n.runListeners(func(listener NotifyListener) {
  2278  		listener.TeamDeleted(teamID)
  2279  	})
  2280  	n.G().Log.CDebugf(ctx, "- Sent TeamDeleted notification")
  2281  }
  2282  
  2283  func (n *NotifyRouter) HandleTeamExit(ctx context.Context, teamID keybase1.TeamID) {
  2284  	if n == nil {
  2285  		return
  2286  	}
  2287  
  2288  	var wg sync.WaitGroup
  2289  	n.G().Log.CDebugf(ctx, "+ Sending TeamExit notification")
  2290  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2291  		if n.getNotificationChannels(id).Team {
  2292  			wg.Add(1)
  2293  			go func() {
  2294  				_ = (keybase1.NotifyTeamClient{
  2295  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2296  				}).TeamExit(context.Background(), teamID)
  2297  				wg.Done()
  2298  			}()
  2299  		}
  2300  		return true
  2301  	})
  2302  	wg.Wait()
  2303  
  2304  	n.runListeners(func(listener NotifyListener) {
  2305  		listener.TeamExit(teamID)
  2306  	})
  2307  	n.G().Log.CDebugf(ctx, "- Sent TeamExit notification")
  2308  }
  2309  
  2310  func (n *NotifyRouter) HandleTeamRoleMapChanged(ctx context.Context, version keybase1.UserTeamVersion) {
  2311  	if n == nil {
  2312  		return
  2313  	}
  2314  
  2315  	var wg sync.WaitGroup
  2316  	n.G().Log.CDebugf(ctx, "+ Sending TeamRoleMapChanged notification")
  2317  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2318  		if n.getNotificationChannels(id).Team {
  2319  			wg.Add(1)
  2320  			go func() {
  2321  				_ = (keybase1.NotifyTeamClient{
  2322  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2323  				}).TeamRoleMapChanged(context.Background(), version)
  2324  				wg.Done()
  2325  			}()
  2326  		}
  2327  		return true
  2328  	})
  2329  	wg.Wait()
  2330  
  2331  	n.runListeners(func(listener NotifyListener) {
  2332  		listener.TeamRoleMapChanged(version)
  2333  	})
  2334  	n.G().Log.CDebugf(ctx, "- Sent TeamRoleMapChanged notification (version %d)", version)
  2335  }
  2336  
  2337  func (n *NotifyRouter) HandleUserBlocked(ctx context.Context, b keybase1.UserBlockedBody) {
  2338  	if n == nil {
  2339  		return
  2340  	}
  2341  
  2342  	summary := b.Summarize()
  2343  
  2344  	var wg sync.WaitGroup
  2345  	n.G().Log.CDebugf(ctx, "+ Sending UserBlocked notification: %+v", b)
  2346  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2347  		if n.getNotificationChannels(id).Tracking {
  2348  			wg.Add(1)
  2349  			go func() {
  2350  				_ = (keybase1.NotifyTrackingClient{
  2351  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2352  				}).NotifyUserBlocked(context.Background(), summary)
  2353  				wg.Done()
  2354  			}()
  2355  		}
  2356  		return true
  2357  	})
  2358  	wg.Wait()
  2359  
  2360  	n.runListeners(func(listener NotifyListener) {
  2361  		listener.UserBlocked(b)
  2362  	})
  2363  	n.G().Log.CDebugf(ctx, "- Sent UserBlocked notification")
  2364  }
  2365  
  2366  func (n *NotifyRouter) HandleTeamAbandoned(ctx context.Context, teamID keybase1.TeamID) {
  2367  	if n == nil {
  2368  		return
  2369  	}
  2370  
  2371  	var wg sync.WaitGroup
  2372  	n.G().Log.CDebugf(ctx, "+ Sending TeamAbandoned notification")
  2373  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2374  		if n.getNotificationChannels(id).Team {
  2375  			wg.Add(1)
  2376  			go func() {
  2377  				_ = (keybase1.NotifyTeamClient{
  2378  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2379  				}).TeamAbandoned(context.Background(), teamID)
  2380  				wg.Done()
  2381  			}()
  2382  		}
  2383  		return true
  2384  	})
  2385  	wg.Wait()
  2386  
  2387  	n.runListeners(func(listener NotifyListener) {
  2388  		listener.TeamExit(teamID)
  2389  	})
  2390  	n.G().Log.CDebugf(ctx, "- Sent TeamAbandoned notification")
  2391  }
  2392  
  2393  func (n *NotifyRouter) HandleNewlyAddedToTeam(ctx context.Context, teamID keybase1.TeamID) {
  2394  	if n == nil {
  2395  		return
  2396  	}
  2397  
  2398  	var wg sync.WaitGroup
  2399  	n.G().Log.CDebugf(ctx, "+ Sending NewlyAddedToTeam notification")
  2400  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2401  		if n.getNotificationChannels(id).Team {
  2402  			wg.Add(1)
  2403  			go func() {
  2404  				_ = (keybase1.NotifyTeamClient{
  2405  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2406  				}).NewlyAddedToTeam(context.Background(), teamID)
  2407  				wg.Done()
  2408  			}()
  2409  		}
  2410  		return true
  2411  	})
  2412  	wg.Wait()
  2413  
  2414  	n.runListeners(func(listener NotifyListener) {
  2415  		listener.NewlyAddedToTeam(teamID)
  2416  	})
  2417  	n.G().Log.CDebugf(ctx, "- Sent NewlyAddedToTeam notification")
  2418  }
  2419  
  2420  func (n *NotifyRouter) HandleNewTeamEK(ctx context.Context, teamID keybase1.TeamID,
  2421  	generation keybase1.EkGeneration) {
  2422  	if n == nil {
  2423  		return
  2424  	}
  2425  
  2426  	arg := keybase1.NewTeamEkArg{
  2427  		Id:         teamID,
  2428  		Generation: generation,
  2429  	}
  2430  
  2431  	var wg sync.WaitGroup
  2432  	n.G().Log.CDebugf(ctx, "+ Sending NewTeamEK notification")
  2433  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2434  		if n.getNotificationChannels(id).Ephemeral {
  2435  			wg.Add(1)
  2436  			go func() {
  2437  				_ = (keybase1.NotifyEphemeralClient{
  2438  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2439  				}).NewTeamEk(context.Background(), arg)
  2440  				wg.Done()
  2441  			}()
  2442  		}
  2443  		return true
  2444  	})
  2445  	wg.Wait()
  2446  
  2447  	n.runListeners(func(listener NotifyListener) {
  2448  		listener.NewTeamEK(teamID, generation)
  2449  	})
  2450  	n.G().Log.CDebugf(ctx, "- Sent NewTeamEK notification")
  2451  }
  2452  
  2453  func (n *NotifyRouter) HandleNewTeambotEK(ctx context.Context, teamID keybase1.TeamID,
  2454  	generation keybase1.EkGeneration) {
  2455  	if n == nil {
  2456  		return
  2457  	}
  2458  
  2459  	arg := keybase1.NewTeambotEkArg{
  2460  		Id:         teamID,
  2461  		Generation: generation,
  2462  	}
  2463  
  2464  	var wg sync.WaitGroup
  2465  	n.G().Log.CDebugf(ctx, "+ Sending NewTeambotEK notification")
  2466  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2467  		if n.getNotificationChannels(id).Ephemeral {
  2468  			wg.Add(1)
  2469  			go func() {
  2470  				_ = (keybase1.NotifyEphemeralClient{
  2471  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2472  				}).NewTeambotEk(context.Background(), arg)
  2473  				wg.Done()
  2474  			}()
  2475  		}
  2476  		return true
  2477  	})
  2478  	wg.Wait()
  2479  
  2480  	n.runListeners(func(listener NotifyListener) {
  2481  		listener.NewTeambotEK(teamID, generation)
  2482  	})
  2483  	n.G().Log.CDebugf(ctx, "- Sent NewTeambotEK notification")
  2484  }
  2485  
  2486  func (n *NotifyRouter) HandleTeambotEKNeeded(ctx context.Context, teamID keybase1.TeamID,
  2487  	botUID keybase1.UID, generation keybase1.EkGeneration, forceCreateGen *keybase1.EkGeneration) {
  2488  	if n == nil {
  2489  		return
  2490  	}
  2491  
  2492  	arg := keybase1.TeambotEkNeededArg{
  2493  		Id:                    teamID,
  2494  		Uid:                   botUID,
  2495  		Generation:            generation,
  2496  		ForceCreateGeneration: forceCreateGen,
  2497  	}
  2498  
  2499  	var wg sync.WaitGroup
  2500  	n.G().Log.CDebugf(ctx, "+ Sending TeambotEKNeeded notification")
  2501  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2502  		if n.getNotificationChannels(id).Ephemeral {
  2503  			wg.Add(1)
  2504  			go func() {
  2505  				_ = (keybase1.NotifyEphemeralClient{
  2506  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2507  				}).TeambotEkNeeded(context.Background(), arg)
  2508  				wg.Done()
  2509  			}()
  2510  		}
  2511  		return true
  2512  	})
  2513  	wg.Wait()
  2514  
  2515  	n.runListeners(func(listener NotifyListener) {
  2516  		listener.TeambotEKNeeded(teamID, botUID, generation, forceCreateGen)
  2517  	})
  2518  	n.G().Log.CDebugf(ctx, "- Sent TeambotEKNeeded notification")
  2519  }
  2520  
  2521  func (n *NotifyRouter) HandleNewTeambotKey(ctx context.Context, teamID keybase1.TeamID,
  2522  	app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) {
  2523  	if n == nil {
  2524  		return
  2525  	}
  2526  
  2527  	arg := keybase1.NewTeambotKeyArg{
  2528  		Id:          teamID,
  2529  		Application: app,
  2530  		Generation:  generation,
  2531  	}
  2532  
  2533  	var wg sync.WaitGroup
  2534  	n.G().Log.CDebugf(ctx, "+ Sending NewTeambotKey notification")
  2535  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2536  		if n.getNotificationChannels(id).Teambot {
  2537  			wg.Add(1)
  2538  			go func() {
  2539  				_ = (keybase1.NotifyTeambotClient{
  2540  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2541  				}).NewTeambotKey(context.Background(), arg)
  2542  				wg.Done()
  2543  			}()
  2544  		}
  2545  		return true
  2546  	})
  2547  	wg.Wait()
  2548  
  2549  	n.runListeners(func(listener NotifyListener) {
  2550  		listener.NewTeambotKey(teamID, generation)
  2551  	})
  2552  	n.G().Log.CDebugf(ctx, "- Sent NewTeambotKey notification")
  2553  }
  2554  
  2555  func (n *NotifyRouter) HandleTeambotKeyNeeded(ctx context.Context, teamID keybase1.TeamID,
  2556  	botUID keybase1.UID, app keybase1.TeamApplication, generation keybase1.TeambotKeyGeneration) {
  2557  	if n == nil {
  2558  		return
  2559  	}
  2560  
  2561  	arg := keybase1.TeambotKeyNeededArg{
  2562  		Id:          teamID,
  2563  		Application: app,
  2564  		Uid:         botUID,
  2565  		Generation:  generation,
  2566  	}
  2567  
  2568  	var wg sync.WaitGroup
  2569  	n.G().Log.CDebugf(ctx, "+ Sending TeambotKeyNeeded notification")
  2570  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2571  		if n.getNotificationChannels(id).Teambot {
  2572  			wg.Add(1)
  2573  			go func() {
  2574  				_ = (keybase1.NotifyTeambotClient{
  2575  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2576  				}).TeambotKeyNeeded(context.Background(), arg)
  2577  				wg.Done()
  2578  			}()
  2579  		}
  2580  		return true
  2581  	})
  2582  	wg.Wait()
  2583  
  2584  	n.runListeners(func(listener NotifyListener) {
  2585  		listener.TeambotKeyNeeded(teamID, botUID, generation)
  2586  	})
  2587  	n.G().Log.CDebugf(ctx, "- Sent TeambotKeyNeeded notification")
  2588  }
  2589  
  2590  func (n *NotifyRouter) HandleAvatarUpdated(ctx context.Context, name string, formats []keybase1.AvatarFormat,
  2591  	typ keybase1.AvatarUpdateType) {
  2592  	if n == nil {
  2593  		return
  2594  	}
  2595  	arg := keybase1.AvatarUpdatedArg{
  2596  		Name:    name,
  2597  		Formats: formats,
  2598  		Typ:     typ,
  2599  	}
  2600  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2601  		if n.getNotificationChannels(id).Team {
  2602  			go func() {
  2603  				_ = (keybase1.NotifyTeamClient{
  2604  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2605  				}).AvatarUpdated(context.Background(), arg)
  2606  			}()
  2607  		}
  2608  		return true
  2609  	})
  2610  
  2611  	n.runListeners(func(listener NotifyListener) {
  2612  		listener.AvatarUpdated(name, formats)
  2613  	})
  2614  }
  2615  
  2616  func (n *NotifyRouter) HandlePhoneNumbersChanged(ctx context.Context, list []keybase1.UserPhoneNumber, category string, phoneNumber keybase1.PhoneNumber) {
  2617  	if n == nil {
  2618  		return
  2619  	}
  2620  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2621  		if n.getNotificationChannels(id).Team {
  2622  			go func() {
  2623  				_ = (keybase1.NotifyPhoneNumberClient{
  2624  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2625  				}).PhoneNumbersChanged(context.Background(), keybase1.PhoneNumbersChangedArg{
  2626  					List:        list,
  2627  					Category:    category,
  2628  					PhoneNumber: phoneNumber,
  2629  				})
  2630  			}()
  2631  		}
  2632  		return true
  2633  	})
  2634  
  2635  	n.runListeners(func(listener NotifyListener) {
  2636  		listener.PhoneNumbersChanged(list, category, phoneNumber)
  2637  	})
  2638  }
  2639  
  2640  func (n *NotifyRouter) HandleEmailAddressVerified(ctx context.Context, emailAddress keybase1.EmailAddress) {
  2641  	if n == nil {
  2642  		return
  2643  	}
  2644  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2645  		if n.getNotificationChannels(id).Team {
  2646  			go func() {
  2647  				_ = (keybase1.NotifyEmailAddressClient{
  2648  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2649  				}).EmailAddressVerified(context.Background(), emailAddress)
  2650  			}()
  2651  		}
  2652  		return true
  2653  	})
  2654  
  2655  	n.runListeners(func(listener NotifyListener) {
  2656  		listener.EmailAddressVerified(emailAddress)
  2657  	})
  2658  }
  2659  
  2660  func (n *NotifyRouter) HandleEmailAddressChanged(ctx context.Context, list []keybase1.Email, category string, emailAddress keybase1.EmailAddress) {
  2661  	if n == nil {
  2662  		return
  2663  	}
  2664  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2665  		if n.getNotificationChannels(id).Team {
  2666  			go func() {
  2667  				_ = (keybase1.NotifyEmailAddressClient{
  2668  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2669  				}).EmailsChanged(context.Background(), keybase1.EmailsChangedArg{
  2670  					List:     list,
  2671  					Category: category,
  2672  					Email:    emailAddress,
  2673  				})
  2674  			}()
  2675  		}
  2676  		return true
  2677  	})
  2678  
  2679  	n.runListeners(func(listener NotifyListener) {
  2680  		listener.EmailsChanged(list, category, emailAddress)
  2681  	})
  2682  }
  2683  
  2684  func (n *NotifyRouter) HandleChatPaymentInfo(ctx context.Context, uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIPaymentInfo) {
  2685  	if n == nil {
  2686  		return
  2687  	}
  2688  	var wg sync.WaitGroup
  2689  	n.G().Log.CDebugf(ctx, "+ Sending ChatPaymentInfo notification")
  2690  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2691  		if n.shouldSendChatNotification(id, chat1.TopicType_NONE) {
  2692  			wg.Add(1)
  2693  			go func() {
  2694  				_ = (chat1.NotifyChatClient{
  2695  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2696  				}).ChatPaymentInfo(context.Background(), chat1.ChatPaymentInfoArg{
  2697  					Uid:    uid,
  2698  					ConvID: convID,
  2699  					MsgID:  msgID,
  2700  					Info:   info,
  2701  				})
  2702  				wg.Done()
  2703  			}()
  2704  		}
  2705  		return true
  2706  	})
  2707  	wg.Wait()
  2708  
  2709  	n.runListeners(func(listener NotifyListener) {
  2710  		listener.ChatPaymentInfo(uid, convID, msgID, info)
  2711  	})
  2712  	n.G().Log.CDebugf(ctx, "- Sent ChatPaymentInfo notification")
  2713  }
  2714  
  2715  func (n *NotifyRouter) HandleChatRequestInfo(ctx context.Context, uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIRequestInfo) {
  2716  	if n == nil {
  2717  		return
  2718  	}
  2719  	var wg sync.WaitGroup
  2720  	n.G().Log.CDebugf(ctx, "+ Sending ChatRequestInfo notification")
  2721  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2722  		if n.shouldSendChatNotification(id, chat1.TopicType_NONE) {
  2723  			wg.Add(1)
  2724  			go func() {
  2725  				_ = (chat1.NotifyChatClient{
  2726  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2727  				}).ChatRequestInfo(context.Background(), chat1.ChatRequestInfoArg{
  2728  					Uid:    uid,
  2729  					ConvID: convID,
  2730  					MsgID:  msgID,
  2731  					Info:   info,
  2732  				})
  2733  				wg.Done()
  2734  			}()
  2735  		}
  2736  		return true
  2737  	})
  2738  	wg.Wait()
  2739  
  2740  	n.runListeners(func(listener NotifyListener) {
  2741  		listener.ChatRequestInfo(uid, convID, msgID, info)
  2742  	})
  2743  	n.G().Log.CDebugf(ctx, "- Sent ChatRequestInfo notification")
  2744  }
  2745  
  2746  func (n *NotifyRouter) HandlePasswordChanged(ctx context.Context, passphraseState keybase1.PassphraseState) {
  2747  	if n == nil {
  2748  		return
  2749  	}
  2750  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2751  		if n.getNotificationChannels(id).Users {
  2752  			go func() {
  2753  				_ = (keybase1.NotifyUsersClient{
  2754  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2755  				}).PasswordChanged(context.Background(), passphraseState)
  2756  			}()
  2757  		}
  2758  		return true
  2759  	})
  2760  
  2761  	n.runListeners(func(listener NotifyListener) {
  2762  		listener.PasswordChanged()
  2763  	})
  2764  }
  2765  
  2766  // RootAuditError is called when the merkle root auditor finds an invalid skip
  2767  // sequence in a random old block.
  2768  func (n *NotifyRouter) HandleRootAuditError(msg string) {
  2769  	if n == nil {
  2770  		return
  2771  	}
  2772  	n.G().Log.Debug("+ Sending merkle tree audit notification")
  2773  	// For all connections we currently have open...
  2774  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2775  		// If the connection wants the `Session` notification type
  2776  		if n.getNotificationChannels(id).Audit {
  2777  			// In the background do...
  2778  			go func() {
  2779  				// A send of a `RootAuditError` RPC
  2780  				_ = (keybase1.NotifyAuditClient{
  2781  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2782  				}).RootAuditError(context.Background(), msg)
  2783  			}()
  2784  		}
  2785  		return true
  2786  	})
  2787  
  2788  	n.runListeners(func(listener NotifyListener) {
  2789  		listener.RootAuditError(msg)
  2790  	})
  2791  
  2792  	n.G().Log.Debug("- merkle tree audit notification sent")
  2793  }
  2794  
  2795  func (n *NotifyRouter) HandleBoxAuditError(ctx context.Context, msg string) {
  2796  	if n == nil {
  2797  		return
  2798  	}
  2799  	n.G().Log.CDebugf(ctx, "+ Sending BoxAuditError notification")
  2800  	defer n.G().Log.CDebugf(ctx, "- Sending BoxAuditError notification")
  2801  
  2802  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2803  		if n.getNotificationChannels(id).Audit {
  2804  			go func() {
  2805  				_ = (keybase1.NotifyAuditClient{
  2806  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2807  				}).BoxAuditError(context.Background(), msg)
  2808  			}()
  2809  		}
  2810  		return true
  2811  	})
  2812  
  2813  	n.runListeners(func(listener NotifyListener) {
  2814  		listener.BoxAuditError(msg)
  2815  	})
  2816  }
  2817  
  2818  func (n *NotifyRouter) HandleRuntimeStatsUpdate(ctx context.Context, stats *keybase1.RuntimeStats) {
  2819  	if n == nil {
  2820  		return
  2821  	}
  2822  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2823  		if n.getNotificationChannels(id).Runtimestats {
  2824  			go func() {
  2825  				_ = (keybase1.NotifyRuntimeStatsClient{
  2826  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2827  				}).RuntimeStatsUpdate(ctx, stats)
  2828  			}()
  2829  		}
  2830  		return true
  2831  	})
  2832  	n.runListeners(func(listener NotifyListener) {
  2833  		listener.RuntimeStatsUpdate(stats)
  2834  	})
  2835  }
  2836  
  2837  func (n *NotifyRouter) HandleHTTPSrvInfoUpdate(ctx context.Context, info keybase1.HttpSrvInfo) {
  2838  	if n == nil {
  2839  		return
  2840  	}
  2841  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2842  		if n.getNotificationChannels(id).Service {
  2843  			go func() {
  2844  				_ = (keybase1.NotifyServiceClient{
  2845  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2846  				}).HTTPSrvInfoUpdate(ctx, info)
  2847  			}()
  2848  		}
  2849  		return true
  2850  	})
  2851  	n.runListeners(func(listener NotifyListener) {
  2852  		listener.HTTPSrvInfoUpdate(info)
  2853  	})
  2854  }
  2855  
  2856  func (n *NotifyRouter) HandleHandleKeybaseLink(ctx context.Context, link string, deferred bool) {
  2857  	if n == nil {
  2858  		return
  2859  	}
  2860  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2861  		if n.getNotificationChannels(id).Service {
  2862  			go func() {
  2863  				_ = (keybase1.NotifyServiceClient{
  2864  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2865  				}).HandleKeybaseLink(ctx, keybase1.HandleKeybaseLinkArg{
  2866  					Link:     link,
  2867  					Deferred: deferred,
  2868  				})
  2869  			}()
  2870  		}
  2871  		return true
  2872  	})
  2873  	n.runListeners(func(listener NotifyListener) {
  2874  		listener.HandleKeybaseLink(link, deferred)
  2875  	})
  2876  }
  2877  
  2878  func (n *NotifyRouter) HandleIdentifyUpdate(ctx context.Context, okUsernames []string, brokenUsernames []string) {
  2879  	if n == nil {
  2880  		return
  2881  	}
  2882  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2883  		if n.getNotificationChannels(id).Users {
  2884  			go func() {
  2885  				_ = (keybase1.NotifyUsersClient{
  2886  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2887  				}).IdentifyUpdate(ctx, keybase1.IdentifyUpdateArg{
  2888  					OkUsernames:     okUsernames,
  2889  					BrokenUsernames: brokenUsernames,
  2890  				})
  2891  			}()
  2892  		}
  2893  		return true
  2894  	})
  2895  	n.runListeners(func(listener NotifyListener) {
  2896  		listener.IdentifyUpdate(okUsernames, brokenUsernames)
  2897  	})
  2898  }
  2899  
  2900  func (n *NotifyRouter) HandleFeaturedBots(ctx context.Context, bots []keybase1.FeaturedBot, limit, offset int) {
  2901  	if n == nil {
  2902  		return
  2903  	}
  2904  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2905  		if n.getNotificationChannels(id).FeaturedBots {
  2906  			go func() {
  2907  				_ = (keybase1.NotifyFeaturedBotsClient{
  2908  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2909  				}).FeaturedBotsUpdate(ctx, keybase1.FeaturedBotsUpdateArg{
  2910  					Bots:   bots,
  2911  					Limit:  limit,
  2912  					Offset: offset,
  2913  				})
  2914  			}()
  2915  		}
  2916  		return true
  2917  	})
  2918  	n.runListeners(func(listener NotifyListener) {
  2919  		listener.FeaturedBotsUpdate(bots, limit, offset)
  2920  	})
  2921  }
  2922  
  2923  func (n *NotifyRouter) HandleSaltpackOperationStart(ctx context.Context, opType keybase1.SaltpackOperationType, filename string) {
  2924  	if n == nil {
  2925  		return
  2926  	}
  2927  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2928  		if n.getNotificationChannels(id).Saltpack {
  2929  			// note there's no goroutine here on purpose
  2930  			// (notification ordering)
  2931  			_ = (keybase1.NotifySaltpackClient{
  2932  				Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2933  			}).SaltpackOperationStart(context.Background(), keybase1.SaltpackOperationStartArg{
  2934  				OpType:   opType,
  2935  				Filename: filename,
  2936  			})
  2937  		}
  2938  		return true
  2939  	})
  2940  
  2941  	n.runListeners(func(listener NotifyListener) {
  2942  		listener.SaltpackOperationStart(opType, filename)
  2943  	})
  2944  }
  2945  
  2946  func (n *NotifyRouter) HandleSaltpackOperationProgress(ctx context.Context, opType keybase1.SaltpackOperationType, filename string, bytesComplete, bytesTotal int64) {
  2947  	if n == nil {
  2948  		return
  2949  	}
  2950  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2951  		if n.getNotificationChannels(id).Saltpack {
  2952  			// note there's no goroutine here on purpose
  2953  			// (notification ordering)
  2954  			_ = (keybase1.NotifySaltpackClient{
  2955  				Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2956  			}).SaltpackOperationProgress(context.Background(), keybase1.SaltpackOperationProgressArg{
  2957  				OpType:        opType,
  2958  				Filename:      filename,
  2959  				BytesComplete: bytesComplete,
  2960  				BytesTotal:    bytesTotal,
  2961  			})
  2962  		}
  2963  		return true
  2964  	})
  2965  
  2966  	n.runListeners(func(listener NotifyListener) {
  2967  		listener.SaltpackOperationDone(opType, filename)
  2968  	})
  2969  }
  2970  
  2971  func (n *NotifyRouter) HandleSaltpackOperationDone(ctx context.Context, opType keybase1.SaltpackOperationType, filename string) {
  2972  	if n == nil {
  2973  		return
  2974  	}
  2975  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2976  		if n.getNotificationChannels(id).Saltpack {
  2977  			// note there's no goroutine here on purpose
  2978  			// (notification ordering)
  2979  			_ = (keybase1.NotifySaltpackClient{
  2980  				Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  2981  			}).SaltpackOperationDone(context.Background(), keybase1.SaltpackOperationDoneArg{
  2982  				OpType:   opType,
  2983  				Filename: filename,
  2984  			})
  2985  		}
  2986  		return true
  2987  	})
  2988  
  2989  	n.runListeners(func(listener NotifyListener) {
  2990  		listener.SaltpackOperationDone(opType, filename)
  2991  	})
  2992  }
  2993  
  2994  func (n *NotifyRouter) HandleUpdateInviteCounts(ctx context.Context, counts keybase1.InviteCounts) {
  2995  	if n == nil {
  2996  		return
  2997  	}
  2998  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  2999  		if n.getNotificationChannels(id).App {
  3000  			go func() {
  3001  				_ = (keybase1.NotifyInviteFriendsClient{
  3002  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  3003  				}).UpdateInviteCounts(context.Background(), counts)
  3004  			}()
  3005  		}
  3006  		return true
  3007  	})
  3008  
  3009  	n.runListeners(func(listener NotifyListener) {
  3010  		listener.UpdateInviteCounts(counts)
  3011  	})
  3012  }
  3013  
  3014  func (n *NotifyRouter) HandleTeamTreeMembershipsPartial(ctx context.Context,
  3015  	result keybase1.TeamTreeMembership) {
  3016  
  3017  	if n == nil {
  3018  		return
  3019  	}
  3020  
  3021  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  3022  		if n.getNotificationChannels(id).Team {
  3023  			go func() {
  3024  				_ = (keybase1.NotifyTeamClient{
  3025  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  3026  				}).TeamTreeMembershipsPartial(context.Background(), result)
  3027  			}()
  3028  		}
  3029  		return true
  3030  	})
  3031  
  3032  	n.runListeners(func(listener NotifyListener) {
  3033  		listener.TeamTreeMembershipsPartial(result)
  3034  	})
  3035  }
  3036  
  3037  func (n *NotifyRouter) HandleTeamTreeMembershipsDone(ctx context.Context, result keybase1.TeamTreeMembershipsDoneResult) {
  3038  	if n == nil {
  3039  		return
  3040  	}
  3041  
  3042  	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
  3043  		if n.getNotificationChannels(id).Team {
  3044  			go func() {
  3045  				_ = (keybase1.NotifyTeamClient{
  3046  					Cli: rpc.NewClient(xp, NewContextifiedErrorUnwrapper(n.G()), nil),
  3047  				}).TeamTreeMembershipsDone(context.Background(), result)
  3048  			}()
  3049  		}
  3050  		return true
  3051  	})
  3052  
  3053  	n.runListeners(func(listener NotifyListener) {
  3054  		listener.TeamTreeMembershipsDone(result)
  3055  	})
  3056  }