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