github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/contacts.go (about) 1 // Copyright 2019 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package service 5 6 import ( 7 "fmt" 8 "sort" 9 "time" 10 11 "github.com/keybase/client/go/contacts" 12 "github.com/keybase/client/go/libkb" 13 keybase1 "github.com/keybase/client/go/protocol/keybase1" 14 "github.com/keybase/client/go/uidmap" 15 "github.com/keybase/go-framed-msgpack-rpc/rpc" 16 "golang.org/x/net/context" 17 ) 18 19 type bulkLookupContactsProvider struct{} 20 21 var _ contacts.ContactsProvider = (*bulkLookupContactsProvider)(nil) 22 23 func (c *bulkLookupContactsProvider) LookupAllWithToken(mctx libkb.MetaContext, emails []keybase1.EmailAddress, 24 numbers []keybase1.RawPhoneNumber, token contacts.Token) (contacts.ContactLookupResults, error) { 25 defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#LookupAllWithToken(len=%d)", len(emails)+len(numbers)), 26 nil)() 27 return contacts.BulkLookupContacts(mctx, emails, numbers, token) 28 } 29 30 func (c *bulkLookupContactsProvider) LookupAll(mctx libkb.MetaContext, emails []keybase1.EmailAddress, 31 numbers []keybase1.RawPhoneNumber) (contacts.ContactLookupResults, error) { 32 defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#LookupAll(len=%d)", len(emails)+len(numbers)), 33 nil)() 34 return c.LookupAllWithToken(mctx, emails, numbers, contacts.NoneToken) 35 } 36 37 func (c *bulkLookupContactsProvider) FindUsernames(mctx libkb.MetaContext, 38 uids []keybase1.UID) (res map[keybase1.UID]contacts.ContactUsernameAndFullName, err error) { 39 defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FillUsernames(len=%d)", len(res)), 40 nil)() 41 42 const fullnameFreshness = 10 * time.Minute 43 const networkTimeBudget = uidmap.DefaultNetworkBudget 44 const forceNetworkForFullNames = true 45 46 nameMap, err := uidmap.MapUIDsReturnMapMctx(mctx, uids, fullnameFreshness, networkTimeBudget, forceNetworkForFullNames) 47 if err != nil { 48 return nil, err 49 } 50 51 res = make(map[keybase1.UID]contacts.ContactUsernameAndFullName) 52 for uid, v := range nameMap { 53 ufp := contacts.ContactUsernameAndFullName{ 54 Username: v.NormalizedUsername.String(), 55 } 56 if fullNamePkg := v.FullName; fullNamePkg != nil { 57 ufp.Fullname = fullNamePkg.FullName.String() 58 } 59 res[uid] = ufp 60 } 61 return res, nil 62 } 63 64 func (c *bulkLookupContactsProvider) FindFollowing(mctx libkb.MetaContext, 65 uids []keybase1.UID) (res map[keybase1.UID]bool, err error) { 66 defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FillFollowing(len=%d)", len(res)), 67 nil)() 68 69 arg := libkb.NewLoadUserArgWithMetaContext(mctx).WithSelf(true).WithStubMode(libkb.StubModeUnstubbed) 70 err = mctx.G().GetFullSelfer().WithUser(arg, func(user *libkb.User) error { 71 mctx.Debug("In WithUser: user found: %t", user != nil) 72 if user == nil { 73 return libkb.UserNotFoundError{} 74 } 75 76 var trackList []*libkb.TrackChainLink 77 idTable := user.IDTable() 78 if idTable != nil { 79 trackList = idTable.GetTrackList() 80 } 81 82 mctx.Debug("In WithUser: idTable exists: %t, trackList len: %d", idTable != nil, len(trackList)) 83 res = make(map[keybase1.UID]bool) 84 if len(trackList) == 0 { 85 // Nothing to do. 86 return nil 87 } 88 89 followedUIDSet := make(map[keybase1.UID]struct{}, len(trackList)) 90 for _, track := range trackList { 91 uid, err := track.GetTrackedUID() 92 if err != nil { 93 return err 94 } 95 96 followedUIDSet[uid] = struct{}{} 97 } 98 99 for _, v := range uids { 100 _, found := followedUIDSet[v] 101 res[v] = found 102 } 103 104 return nil 105 }) 106 107 if err != nil { 108 return nil, err 109 } 110 return res, nil 111 } 112 113 func (c *bulkLookupContactsProvider) FindServiceMaps(mctx libkb.MetaContext, 114 uids []keybase1.UID) (res map[keybase1.UID]libkb.UserServiceSummary, err error) { 115 defer mctx.Trace(fmt.Sprintf("bulkLookupContactsProvider#FindServiceMaps(len=%d)", len(uids)), 116 &err)() 117 118 const serviceMapFreshness = 12 * time.Hour 119 const networkTimeBudget = uidmap.DefaultNetworkBudget 120 pkgs := mctx.G().ServiceMapper.MapUIDsToServiceSummaries(mctx.Ctx(), mctx.G(), 121 uids, serviceMapFreshness, networkTimeBudget) 122 res = make(map[keybase1.UID]libkb.UserServiceSummary, len(pkgs)) 123 for uid, pkg := range pkgs { 124 if pkg.ServiceMap != nil { 125 res[uid] = pkg.ServiceMap 126 } 127 } 128 return res, nil 129 } 130 131 type ContactsHandler struct { 132 libkb.Contextified 133 *BaseHandler 134 135 contactsProvider *contacts.CachedContactsProvider 136 } 137 138 func NewCachedContactsProvider(g *libkb.GlobalContext) *contacts.CachedContactsProvider { 139 return &contacts.CachedContactsProvider{ 140 Provider: &bulkLookupContactsProvider{}, 141 Store: contacts.NewContactCacheStore(g), 142 } 143 } 144 145 func NewContactsHandler(xp rpc.Transporter, g *libkb.GlobalContext, provider *contacts.CachedContactsProvider) *ContactsHandler { 146 handler := &ContactsHandler{ 147 Contextified: libkb.NewContextified(g), 148 BaseHandler: NewBaseHandler(g, xp), 149 contactsProvider: provider, 150 } 151 return handler 152 } 153 154 var _ keybase1.ContactsInterface = (*ContactsHandler)(nil) 155 156 func (h *ContactsHandler) LookupContactList(ctx context.Context, arg keybase1.LookupContactListArg) (res []keybase1.ProcessedContact, err error) { 157 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("LOOKCON") 158 defer mctx.Trace(fmt.Sprintf("ContactsHandler#LookupContactList(len=%d)", len(arg.Contacts)), 159 &err)() 160 return contacts.ResolveContacts(mctx, h.contactsProvider, arg.Contacts) 161 } 162 163 func (h *ContactsHandler) SaveContactList(ctx context.Context, arg keybase1.SaveContactListArg) (res keybase1.ContactListResolutionResult, err error) { 164 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("SAVECON") 165 defer mctx.Trace(fmt.Sprintf("ContactsHandler#SaveContactList(len=%d)", len(arg.Contacts)), 166 &err)() 167 return contacts.ResolveAndSaveContacts(mctx, h.contactsProvider, arg.Contacts) 168 } 169 170 func (h *ContactsHandler) LookupSavedContactsList(ctx context.Context, sessionID int) (res []keybase1.ProcessedContact, err error) { 171 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("LOADCON") 172 defer mctx.Trace("ContactsHandler#LookupSavedContactsList", &err)() 173 174 store := h.G().SyncedContactList 175 savedContacts, err := store.RetrieveContacts(mctx) 176 if err != nil { 177 return nil, err 178 } 179 return savedContacts, nil 180 } 181 182 func (h *ContactsHandler) GetContactsForUserRecommendations(ctx context.Context, sessionID int) (res []keybase1.ProcessedContact, err error) { 183 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("RECSCON") 184 defer mctx.Trace("ContactsHandler#GetContactsForUserRecommendations", &err)() 185 186 savedContacts, err := h.G().SyncedContactList.RetrieveContacts(mctx) 187 if err != nil { 188 return nil, err 189 } 190 191 // Allocate space for the number of all saved contacts - we will likely 192 // return less, though. 193 res = make([]keybase1.ProcessedContact, 0, len(savedContacts)) 194 195 // Find contacts that have at least one resolved component, we are going to 196 // take only the resolved component from them (chose one if there are 197 // multiple). 198 seenResolvedContacts := make(map[int]struct{}) 199 for _, contact := range savedContacts { 200 if contact.Resolved { 201 seenResolvedContacts[contact.ContactIndex] = struct{}{} 202 } 203 } 204 205 // Find the best contact for each resolved username. 206 // Map usernames to index in `res` list. 207 contactForUsername := make(map[string]int, len(seenResolvedContacts)) 208 currentUID := mctx.CurrentUID() 209 210 for _, contact := range savedContacts { 211 if !contact.Resolved { 212 if _, found := seenResolvedContacts[contact.ContactIndex]; found { 213 // This contact has a resolved component, skip unresolved ones 214 // completely. 215 continue 216 } 217 218 res = append(res, contact) 219 } else { 220 if contact.Uid.Equal(currentUID) { 221 // Some people have their phone number in contact list, do not 222 // show current user in recommendations. 223 continue 224 } 225 226 if currentIndex, found := contactForUsername[contact.Username]; found { 227 current := res[currentIndex] 228 var overwrite bool 229 // NOTE: add more rules here if needed. 230 if current.Component.Email == nil && contact.Component.Email != nil { 231 // Prefer email components to phone ones. 232 overwrite = true 233 } 234 235 if overwrite { 236 res[currentIndex] = contact 237 } 238 } else { 239 contactForUsername[contact.Username] = len(res) 240 res = append(res, contact) 241 } 242 } 243 } 244 245 sort.Slice(res, func(i, j int) bool { 246 return res[i].DisplayName < res[j].DisplayName 247 }) 248 249 return res, nil 250 }