1import * as Container from '../../util/container' 2import * as Constants from '../../constants/profile' 3import * as ProfileGen from '../profile-gen' 4import * as RPCTypes from '../../constants/types/rpc-gen' 5import * as RouteTreeGen from '../route-tree-gen' 6import * as Saga from '../../util/saga' 7import * as TrackerConstants from '../../constants/tracker2' 8import * as Tracker2Gen from '../tracker2-gen' 9import logger from '../../logger' 10import openURL from '../../util/open-url' 11import {RPCError} from '../../util/errors' 12import {pgpSaga} from './pgp' 13import {proofsSaga} from './proofs' 14 15const editProfile = async (state: Container.TypedState, action: ProfileGen.EditProfilePayload) => { 16 await RPCTypes.userProfileEditRpcPromise( 17 { 18 bio: action.payload.bio, 19 fullName: action.payload.fullname, 20 location: action.payload.location, 21 }, 22 TrackerConstants.waitingKey 23 ) 24 return Tracker2Gen.createShowUser({asTracker: false, username: state.config.username}) 25} 26 27const uploadAvatar = async (action: ProfileGen.UploadAvatarPayload) => { 28 try { 29 await RPCTypes.userUploadUserAvatarRpcPromise( 30 { 31 crop: action.payload.crop, 32 filename: action.payload.filename, 33 }, 34 Constants.uploadAvatarWaitingKey 35 ) 36 return RouteTreeGen.createNavigateUp() 37 } catch (e) { 38 // error displayed in component 39 logger.warn(`Error uploading user avatar: ${e.message}`) 40 return false 41 } 42} 43 44const finishRevoking = (state: Container.TypedState) => [ 45 Tracker2Gen.createShowUser({asTracker: false, username: state.config.username}), 46 Tracker2Gen.createLoad({ 47 assertion: state.config.username, 48 guiID: TrackerConstants.generateGUIID(), 49 inTracker: false, 50 reason: '', 51 }), 52 ProfileGen.createRevokeFinish(), 53] 54 55const showUserProfile = (action: ProfileGen.ShowUserProfilePayload) => { 56 const {username} = action.payload 57 return [ 58 ...(Container.isMobile ? [RouteTreeGen.createClearModals()] : []), 59 RouteTreeGen.createNavigateAppend({path: [{props: {username}, selected: 'profile'}]}), 60 ] 61} 62 63const onClickAvatar = (action: ProfileGen.OnClickAvatarPayload) => { 64 if (!action.payload.username) { 65 return 66 } 67 68 if (!action.payload.openWebsite) { 69 return ProfileGen.createShowUserProfile({username: action.payload.username}) 70 } else { 71 openURL(`https://keybase.io/${action.payload.username}`) 72 return undefined 73 } 74} 75 76const submitRevokeProof = async ( 77 state: Container.TypedState, 78 action: ProfileGen.SubmitRevokeProofPayload 79) => { 80 const you = TrackerConstants.getDetails(state, state.config.username) 81 if (!you || !you.assertions) return null 82 const proof = [...you.assertions.values()].find(a => a.sigID === action.payload.proofId) 83 if (!proof) return null 84 85 if (proof.type === 'pgp') { 86 try { 87 await RPCTypes.revokeRevokeKeyRpcPromise({keyID: proof.kid}, Constants.waitingKey) 88 return false 89 } catch (e) { 90 logger.info('error in dropping pgp key', e) 91 return ProfileGen.createRevokeFinish({error: `Error in dropping Pgp Key: ${e}`}) 92 } 93 } else { 94 try { 95 await RPCTypes.revokeRevokeSigsRpcPromise( 96 {sigIDQueries: [action.payload.proofId]}, 97 Constants.waitingKey 98 ) 99 return ProfileGen.createFinishRevoking() 100 } catch (error) { 101 logger.warn(`Error when revoking proof ${action.payload.proofId}`, error) 102 return ProfileGen.createRevokeFinish({ 103 error: 'There was an error revoking your proof. You can click the button to try again.', 104 }) 105 } 106 } 107} 108 109const submitBlockUser = async (action: ProfileGen.SubmitBlockUserPayload) => { 110 try { 111 await RPCTypes.userBlockUserRpcPromise({username: action.payload.username}, Constants.blockUserWaitingKey) 112 return [ 113 ProfileGen.createFinishBlockUser(), 114 Tracker2Gen.createLoad({ 115 assertion: action.payload.username, 116 guiID: TrackerConstants.generateGUIID(), 117 inTracker: false, 118 reason: '', 119 }), 120 ] 121 } catch (e) { 122 const error: RPCError = e 123 logger.warn(`Error blocking user ${action.payload.username}`, error) 124 return ProfileGen.createFinishBlockUser({ 125 error: error.desc || `There was an error blocking ${action.payload.username}.`, 126 }) 127 } 128} 129 130const submitUnblockUser = async (action: ProfileGen.SubmitUnblockUserPayload) => { 131 try { 132 await RPCTypes.userUnblockUserRpcPromise( 133 {username: action.payload.username}, 134 Constants.blockUserWaitingKey 135 ) 136 return Tracker2Gen.createLoad({ 137 assertion: action.payload.username, 138 guiID: TrackerConstants.generateGUIID(), 139 inTracker: false, 140 reason: '', 141 }) 142 } catch (e) { 143 const error: RPCError = e 144 logger.warn(`Error unblocking user ${action.payload.username}`, error) 145 return Tracker2Gen.createUpdateResult({ 146 guiID: action.payload.guiID, 147 reason: `Failed to unblock ${action.payload.username}: ${error.desc}`, 148 result: 'error', 149 }) 150 } 151} 152 153const hideStellar = async (_: Container.TypedState, action: ProfileGen.HideStellarPayload) => { 154 try { 155 await RPCTypes.apiserverPostRpcPromise( 156 { 157 args: [{key: 'hidden', value: action.payload.hidden ? '1' : '0'}], 158 endpoint: 'stellar/hidden', 159 }, 160 TrackerConstants.waitingKey 161 ) 162 } catch (e) { 163 logger.warn('Error setting Stellar hidden:', e) 164 } 165} 166const editAvatar = () => 167 Container.isMobile 168 ? undefined // handled in platform specific 169 : RouteTreeGen.createNavigateAppend({path: [{props: {image: null}, selected: 'profileEditAvatar'}]}) 170 171const backToProfile = (state: Container.TypedState) => [ 172 RouteTreeGen.createNavigateUp(), 173 Tracker2Gen.createShowUser({asTracker: false, username: state.config.username}), 174] 175 176const wotVouch = async ( 177 state: Container.TypedState, 178 action: ProfileGen.WotVouchPayload, 179 logger: Saga.SagaLogger 180) => { 181 const {guiID, otherText, proofs, statement, username, verificationType} = action.payload 182 const details = state.tracker2.usernameToDetails.get(username) 183 if (!details) { 184 return ProfileGen.createWotVouchSetError({error: 'Missing user details.'}) 185 } else if (details.state !== 'valid') { 186 return ProfileGen.createWotVouchSetError({error: `User is not in a valid state. (${details.state})`}) 187 } else if (details.resetBrokeTrack) { 188 return ProfileGen.createWotVouchSetError({error: 'User has reset their account since following.'}) 189 } 190 try { 191 await RPCTypes.wotWotVouchRpcPromise( 192 { 193 confidence: { 194 other: otherText, 195 proofs, 196 usernameVerifiedVia: verificationType, 197 }, 198 guiID, 199 username, 200 vouchText: statement, 201 }, 202 Constants.wotAuthorWaitingKey 203 ) 204 } catch (e) { 205 logger.warn('Error from wotVouch:', e) 206 return ProfileGen.createWotVouchSetError({ 207 error: e.desc || `There was an error submitting the claim.`, 208 }) 209 } 210 return [ProfileGen.createWotVouchSetError({error: ''}), RouteTreeGen.createClearModals()] 211} 212 213function* _profileSaga() { 214 yield* Saga.chainAction2(ProfileGen.submitRevokeProof, submitRevokeProof) 215 yield* Saga.chainAction(ProfileGen.submitBlockUser, submitBlockUser) 216 yield* Saga.chainAction(ProfileGen.submitUnblockUser, submitUnblockUser) 217 yield* Saga.chainAction2(ProfileGen.backToProfile, backToProfile) 218 yield* Saga.chainAction2(ProfileGen.editProfile, editProfile) 219 yield* Saga.chainAction(ProfileGen.uploadAvatar, uploadAvatar) 220 yield* Saga.chainAction2(ProfileGen.finishRevoking, finishRevoking) 221 yield* Saga.chainAction(ProfileGen.onClickAvatar, onClickAvatar) 222 yield* Saga.chainAction(ProfileGen.showUserProfile, showUserProfile) 223 yield* Saga.chainAction2(ProfileGen.editAvatar, editAvatar) 224 yield* Saga.chainAction2(ProfileGen.hideStellar, hideStellar) 225 yield* Saga.chainAction2(ProfileGen.wotVouch, wotVouch) 226} 227 228function* profileSaga() { 229 yield Saga.spawn(_profileSaga) 230 yield Saga.spawn(pgpSaga) 231 yield Saga.spawn(proofsSaga) 232} 233 234export default profileSaga 235