1import * as Constants from '../constants/tracker2' 2import * as Types from '../constants/types/tracker2' 3import * as ConfigGen from '../actions/config-gen' 4import * as Tracker2Gen from '../actions/tracker2-gen' 5import * as Container from '../util/container' 6import * as EngineGen from '../actions/engine-gen-gen' 7import * as RpcTypes from '../constants/types/rpc-gen' 8import {mapGetEnsureValue} from '../util/map' 9import logger from '../logger' 10 11const initialState: Types.State = Constants.makeState() 12 13const getDetails = (state: Types.State, username: string) => 14 mapGetEnsureValue(state.usernameToDetails, username, {...Constants.noDetails}) 15 16type Actions = 17 | Tracker2Gen.Actions 18 | ConfigGen.BootstrapStatusLoadedPayload 19 | EngineGen.Keybase1NotifyTrackingNotifyUserBlockedPayload 20 | EngineGen.Keybase1Identify3UiIdentify3UpdateRowPayload 21 | EngineGen.Keybase1Identify3UiIdentify3UserResetPayload 22 | EngineGen.Keybase1Identify3UiIdentify3UpdateUserCardPayload 23 | EngineGen.Keybase1Identify3UiIdentify3SummaryPayload 24 25export default Container.makeReducer<Actions, Types.State>(initialState, { 26 [Tracker2Gen.resetStore]: () => initialState, 27 [ConfigGen.bootstrapStatusLoaded]: (draftState, action) => { 28 const {username, fullname} = action.payload 29 getDetails(draftState, username).fullname = fullname 30 }, 31 [Tracker2Gen.load]: (draftState, action) => { 32 const {guiID, forceDisplay, assertion, reason} = action.payload 33 const username = assertion 34 if (forceDisplay) { 35 logger.info(`Showing tracker for assertion: ${assertion}`) 36 } 37 const d = getDetails(draftState, username) 38 d.assertions = new Map() // just remove for now, maybe keep them 39 d.guiID = guiID 40 d.reason = reason 41 d.showTracker = forceDisplay || d.showTracker // show it or keep the last state 42 d.state = 'checking' 43 d.username = username 44 }, 45 [Tracker2Gen.updateResult]: (draftState, action) => { 46 const {guiID} = action.payload 47 const username = Constants.guiIDToUsername(draftState, guiID) 48 if (!username) { 49 return 50 } 51 52 const {reason, result} = action.payload 53 const newReason = 54 reason || 55 (result === 'broken' && `Some of ${username}'s proofs have changed since you last followed them.`) 56 57 const d = getDetails(draftState, username) 58 // Don't overwrite the old reason if the user reset. 59 if (!d.resetBrokeTrack || d.reason.length === 0) { 60 d.reason = newReason || d.reason 61 } 62 if (result === 'valid') { 63 d.resetBrokeTrack = false 64 } 65 d.state = result 66 }, 67 [Tracker2Gen.closeTracker]: (draftState, action) => { 68 const {guiID} = action.payload 69 const username = Constants.guiIDToUsername(draftState, guiID) 70 if (!username) { 71 return 72 } 73 74 logger.info(`Closing tracker for assertion: ${username}`) 75 const d = getDetails(draftState, username) 76 d.showTracker = false 77 }, 78 [Tracker2Gen.updateFollows]: (draftState, action) => { 79 const {username, followers, following} = action.payload 80 const d = getDetails(draftState, username) 81 if (followers) { 82 d.followers = new Set(followers.map(f => f.username)) 83 d.followersCount = d.followers.size 84 } 85 if (following) { 86 d.following = new Set(following.map(f => f.username)) 87 d.followingCount = d.following.size 88 } 89 }, 90 [Tracker2Gen.updateWotEntries]: (draftState, action) => { 91 const d = getDetails(draftState, action.payload.voucheeUsername) 92 d.webOfTrustEntries = action.payload.entries 93 }, 94 [Tracker2Gen.proofSuggestionsUpdated]: (draftState, action) => { 95 draftState.proofSuggestions = Container.castDraft(action.payload.suggestions) 96 }, 97 [Tracker2Gen.loadedNonUserProfile]: (draftState, action) => { 98 const {assertion, ...rest} = action.payload 99 const {usernameToNonUserDetails} = draftState 100 const old = usernameToNonUserDetails.get(assertion) || Constants.noNonUserDetails 101 usernameToNonUserDetails.set(assertion, { 102 ...old, 103 ...rest, 104 }) 105 }, 106 // This allows the server to send us a notification to *remove* (not add) 107 // arbitrary followers from arbitrary tracker2 results, so we can hide 108 // blocked users from follower lists. 109 [EngineGen.keybase1NotifyTrackingNotifyUserBlocked]: (draftState, action) => { 110 const {blocker, blocks} = action.payload.params.b 111 const d = getDetails(draftState, blocker) 112 const toProcess = Object.entries(blocks ?? {}).map( 113 ([username, userBlocks]) => [username, getDetails(draftState, username), userBlocks || []] as const 114 ) 115 toProcess.forEach(([username, det, userBlocks]) => { 116 userBlocks.forEach(blockState => { 117 if (blockState.blockType === RpcTypes.UserBlockType.chat) { 118 det.blocked = blockState.blocked 119 } else if (blockState.blockType === RpcTypes.UserBlockType.follow) { 120 det.hidFromFollowers = blockState.blocked 121 blockState.blocked && d.followers && d.followers.delete(username) 122 } 123 }) 124 }) 125 d.followersCount = d.followers?.size 126 }, 127 [EngineGen.keybase1Identify3UiIdentify3UpdateRow]: (draftState, action) => { 128 const {row} = action.payload.params 129 const {guiID} = row 130 const username = Constants.guiIDToUsername(draftState, guiID) 131 if (!username) { 132 return 133 } 134 135 const d = getDetails(draftState, username) 136 const assertions = d.assertions ?? new Map() 137 d.assertions = assertions 138 const assertion = Constants.rpcAssertionToAssertion(row) 139 assertions.set(assertion.assertionKey, assertion) 140 }, 141 [EngineGen.keybase1Identify3UiIdentify3UserReset]: (draftState, action) => { 142 const {guiID} = action.payload.params 143 const username = Constants.guiIDToUsername(draftState, guiID) 144 if (!username) { 145 return 146 } 147 148 const d = getDetails(draftState, username) 149 d.resetBrokeTrack = true 150 d.reason = `${username} reset their account since you last followed them.` 151 }, 152 [EngineGen.keybase1Identify3UiIdentify3UpdateUserCard]: (draftState, action) => { 153 const {guiID, card} = action.payload.params 154 const username = Constants.guiIDToUsername(draftState, guiID) 155 if (!username) { 156 return 157 } 158 159 const {bio, blocked, fullName, hidFromFollowers, location, stellarHidden, teamShowcase} = card 160 const {unverifiedNumFollowers, unverifiedNumFollowing} = card 161 const d = getDetails(draftState, username) 162 d.bio = bio 163 d.blocked = blocked 164 // These will be overridden by a later updateFollows, if it happens (will 165 // happen when viewing profile, but not in tracker pop up. 166 d.followersCount = unverifiedNumFollowers 167 d.followingCount = unverifiedNumFollowing 168 d.fullname = fullName 169 d.location = location 170 d.stellarHidden = stellarHidden 171 d.teamShowcase = 172 teamShowcase?.map(t => ({ 173 description: t.description, 174 isOpen: t.open, 175 membersCount: t.numMembers, 176 name: t.fqName, 177 publicAdmins: t.publicAdmins ?? [], 178 })) ?? [] 179 d.hidFromFollowers = hidFromFollowers 180 }, 181 [EngineGen.keybase1Identify3UiIdentify3Summary]: (draftState, action) => { 182 const {summary} = action.payload.params 183 const {numProofsToCheck, guiID} = summary 184 const username = Constants.guiIDToUsername(draftState, guiID) 185 if (!username) { 186 return 187 } 188 189 const d = getDetails(draftState, username) 190 d.numAssertionsExpected = numProofsToCheck 191 }, 192}) 193