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