func (n *NotifyRouter) HandlePaperKeyCached(uid keybase1.UID, encKID keybase1.KID, sigKID keybase1.KID) {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending paperkey cached notfication")
	arg := keybase1.PaperKeyCachedArg{
		Uid:    uid,
		EncKID: encKID,
		SigKID: sigKID,
	}
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Paperkeys {
			go func() {
				(keybase1.NotifyPaperKeyClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).PaperKeyCached(context.TODO(), arg)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.PaperKeyCached(uid, encKID, sigKID)
	}
	n.G().Log.Debug("- Sent paperkey cached notfication")
}
HandlePaperKeyCached is called whenever a paper key is cached
in response to a rekey harassment.
package libkb
import (
	"sync"
	"time"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
	context "golang.org/x/net/context"
)
type getObj struct {
	id    ConnectionID
	retCh chan<- keybase1.NotificationChannels
}
type setObj struct {
	id  ConnectionID
	val keybase1.NotificationChannels
}
type NotifyListener interface {
	Logout()
	Login(username string)
	ClientOutOfDate(to, uri, msg string)
	UserChanged(uid keybase1.UID)
	TrackingChanged(uid keybase1.UID, username string)
	FSActivity(activity keybase1.FSNotification)
	FavoritesChanged(uid keybase1.UID)
	PaperKeyCached(uid keybase1.UID, encKID keybase1.KID, sigKID keybase1.KID)
	KeyfamilyChanged(uid keybase1.UID)
	NewChatActivity(uid keybase1.UID, activity keybase1.ChatActivity)
}
type NotifyRouter struct {
	Contextified
	cm         *ConnectionManager
	state      map[ConnectionID]keybase1.NotificationChannels
	setCh      chan setObj
	getCh      chan getObj
	shutdownCh chan struct{}
	listener   NotifyListener
}
func NewNotifyRouter(g *GlobalContext) *NotifyRouter {
	ret := &NotifyRouter{
		Contextified: NewContextified(g),
		cm:           g.ConnectionManager,
		state:        make(map[ConnectionID]keybase1.NotificationChannels),
		setCh:        make(chan setObj),
		getCh:        make(chan getObj),
		shutdownCh:   make(chan struct{}),
	}
	go ret.run()
	return ret
}
func (n *NotifyRouter) SetListener(listener NotifyListener) {
	n.listener = listener
}
func (n *NotifyRouter) Shutdown() {
	n.shutdownCh <- struct{}{}
}
func (n *NotifyRouter) setNotificationChannels(id ConnectionID, val keybase1.NotificationChannels) {
	n.setCh <- setObj{id, val}
}
func (n *NotifyRouter) getNotificationChannels(id ConnectionID) keybase1.NotificationChannels {
	retCh := make(chan keybase1.NotificationChannels)
	n.getCh <- getObj{id, retCh}
	return <-retCh
}
func (n *NotifyRouter) run() {
	for {
		select {
		case <-n.shutdownCh:
			return
		case o := <-n.setCh:
			n.state[o.id] = o.val
		case o := <-n.getCh:
			o.retCh <- n.state[o.id]
		}
	}
}
func (n *NotifyRouter) AddConnection(xp rpc.Transporter, ch chan error) ConnectionID {
	if n == nil {
		return 0
	}
	id := n.cm.AddConnection(xp, ch)
	n.setNotificationChannels(id, keybase1.NotificationChannels{})
	return id
}
func (n *NotifyRouter) SetChannels(i ConnectionID, nc keybase1.NotificationChannels) {
	n.setNotificationChannels(i, nc)
}
func (n *NotifyRouter) HandleLogout() {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending logout notification")
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Session {
			go func() {
				(keybase1.NotifySessionClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).LoggedOut(context.TODO())
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.Logout()
	}
	n.G().Log.Debug("- Logout notification sent")
}
func (n *NotifyRouter) HandleLogin(u string) {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending login notfication, as user %q", u)
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Session {
			go func() {
				(keybase1.NotifySessionClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).LoggedIn(context.TODO(), u)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.Login(u)
	}
	n.G().Log.Debug("- Login notification sent")
}
func (n *NotifyRouter) HandleClientOutOfDate(upgradeTo, upgradeURI, upgradeMsg string) {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending client-out-of-date notfication")
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Session {
			go func() {
				(keybase1.NotifySessionClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).ClientOutOfDate(context.TODO(), keybase1.ClientOutOfDateArg{
					UpgradeTo:  upgradeTo,
					UpgradeURI: upgradeURI,
					UpgradeMsg: upgradeMsg,
				})
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.ClientOutOfDate(upgradeTo, upgradeURI, upgradeMsg)
	}
	n.G().Log.Debug("- client-out-of-date notification sent")
}
func (n *NotifyRouter) HandleUserChanged(uid keybase1.UID) {
	if n == nil {
		return
	}
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Users {
			go func() {
				(keybase1.NotifyUsersClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).UserChanged(context.TODO(), uid)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.UserChanged(uid)
	}
}
func (n *NotifyRouter) HandleTrackingChanged(uid keybase1.UID, username string) {
	if n == nil {
		return
	}
	arg := keybase1.TrackingChangedArg{
		Uid:      uid,
		Username: username,
	}
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Tracking {
			go func() {
				(keybase1.NotifyTrackingClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).TrackingChanged(context.TODO(), arg)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.TrackingChanged(uid, username)
	}
}
func (n *NotifyRouter) HandleFSActivity(activity keybase1.FSNotification) {
	if n == nil {
		return
	}
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Kbfs {
			go func() {
				(keybase1.NotifyFSClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).FSActivity(context.TODO(), activity)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.FSActivity(activity)
	}
}
func (n *NotifyRouter) HandleFavoritesChanged(uid keybase1.UID) {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending favorites changed notfication")
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Favorites {
			go func() {
				(keybase1.NotifyFavoritesClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).FavoritesChanged(context.TODO(), uid)
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.FavoritesChanged(uid)
	}
	n.G().Log.Debug("- Sent favorites changed notfication")
}
func (n *NotifyRouter) HandleNewChatActivity(ctx context.Context, uid keybase1.UID, activity *keybase1.ChatActivity) {
	if n == nil {
		return
	}
	n.G().Log.Debug("+ Sending NewChatActivity notfication")
	n.cm.ApplyAll(func(id ConnectionID, xp rpc.Transporter) bool {
		if n.getNotificationChannels(id).Chat {
			go func() {
				(keybase1.NotifyChatClient{
					Cli: rpc.NewClient(xp, ErrorUnwrapper{}),
				}).NewChatActivity(ctx, keybase1.NewChatActivityArg{
					Uid:      uid,
					Activity: *activity,
				})
			}()
		}
		return true
	})
	if n.listener != nil {
		n.listener.NewChatActivity(uid, *activity)
	}
	n.G().Log.Debug("- Sent NewChatActivity notfication")
}