diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 30266fe24..85e8585bf 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -11,7 +11,7 @@ import { } from '@utils' import { isCoreEndpointDisabled } from '@utils/window-utils' import { transactions, config, network } from 'blockstack' - +import { fetchIdentitySettings } from '../../../account/utils' import roundTo from 'round-to' import * as types from './types' import log4js from 'log4js' @@ -41,7 +41,8 @@ function createAccount( bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } = getBlockchainIdentities(masterKeychain, identitiesToGenerate) return { @@ -51,7 +52,8 @@ function createAccount( bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } } @@ -547,6 +549,65 @@ function usedIdentityAddress() { } } +function refreshAllIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + ownerAddresses: Array, + identityKeyPairs: Array +) { + return dispatch => { + const promises: Array> = ownerAddresses.map((address, index) => { + const promise: Promise<*> = new Promise((resolve, reject) => { + const keyPair = identityKeyPairs[index] + return fetchIdentitySettings(api, address, keyPair) + .then((settings) => { + resolve(settings) + }) + .catch(error => reject(error)) + }) + return promise + }) + + return Promise.all(promises) + .then(settings => { + return dispatch(updateAllIdentitySettings(settings)) + }) + .catch((error) => { + logger.error( + 'refreshIdentitySettings: error refreshing identity settings', + error + ) + return Promise.reject(error) + }) + } +} + +function refreshIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + identityIndex: int, + ownerAddress: string, + identityKeyPair: { key: string } +) { + return dispatch => fetchIdentitySettings(api, ownerAddress, identityKeyPair) + .then((settings) => { + return dispatch(updateIdentitySettings(identityIndex, settings)) + }) +} + +function updateAllIdentitySettings(settings) { + return { + type: types.UPDATE_ALL_IDENTITY_SETTINGS, + settings + } +} + +function updateIdentitySettings(identityIndex, settings) { + return { + type: types.UPDATE_IDENTITY_SETTINGS, + identityIndex, + settings + } +} + const AccountActions = { createAccount, updateBackupPhrase, @@ -569,7 +630,11 @@ const AccountActions = { usedIdentityAddress, displayedRecoveryCode, newIdentityAddress, - updateEmail + updateEmail, + refreshAllIdentitySettings, + refreshIdentitySettings, + updateAllIdentitySettings, + updateIdentitySettings } export default AccountActions diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 66c2c3910..6f343f9a1 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -9,7 +9,8 @@ const initialState = { encryptedBackupPhrase: null, // persist identityAccount: { addresses: [], - keypairs: [] + keypairs: [], + settings: [] }, bitcoinAccount: { addresses: [], @@ -43,6 +44,7 @@ function AccountReducer(state = initialState, action) { publicKeychain: action.identityPublicKeychain, addresses: action.identityAddresses, keypairs: action.identityKeypairs, + settings: action.identitySettings, addressIndex: 0 }, bitcoinAccount: { @@ -258,13 +260,28 @@ function AccountReducer(state = initialState, action) { ...state.identityAccount.addresses, action.keypair.address ], - keypairs: [...state.identityAccount.keypairs, action.keypair] + keypairs: [...state.identityAccount.keypairs, action.keypair], + settings: [...state.identityAccount.settings, {}] }) }) case types.CONNECTED_STORAGE: return Object.assign({}, state, { connectedStorageAtLeastOnce: true }) + case types.UPDATE_ALL_IDENTITY_SETTINGS: + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: action.settings + }) + }) + case types.UPDATE_IDENTITY_SETTINGS: + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: state.identityAccount.settings.map( + (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow + ) + }) + }) default: return state } diff --git a/app/js/account/store/account/types.js b/app/js/account/store/account/types.js index 2b4ecd189..cc25721e5 100644 --- a/app/js/account/store/account/types.js +++ b/app/js/account/store/account/types.js @@ -23,3 +23,5 @@ export const RECOVERY_CODE_VERIFIED = 'account/RECOVERY_CODE_VERIFIED' export const INCREMENT_IDENTITY_ADDRESS_INDEX = 'account/INCREMENT_IDENTITY_ADDRESS_INDEX' export const CONNECTED_STORAGE = 'account/CONNECTED_STORAGE' export const UPDATE_EMAIL_ADDRESS = 'account/UPDATE_EMAIL_ADDRESS' +export const UPDATE_ALL_IDENTITY_SETTINGS = 'account/UPDATE_ALL_IDENTITY_SETTINGS' +export const UPDATE_IDENTITY_SETTINGS = 'account/UPDATE_IDENTITY_SETTINGS' diff --git a/app/js/account/utils/index.js b/app/js/account/utils/index.js index 550432406..c59290bae 100644 --- a/app/js/account/utils/index.js +++ b/app/js/account/utils/index.js @@ -4,6 +4,7 @@ import { parseZoneFile } from 'zone-file' import type { GaiaHubConfig } from './blockstack-inc' import { connectToGaiaHub, uploadToGaiaHub } from './blockstack-inc' +import { encryptContent, decryptContent } from 'blockstack' import { getTokenFileUrlFromZoneFile } from '@utils/zone-utils' import log4js from 'log4js' @@ -11,6 +12,7 @@ const logger = log4js.getLogger(__filename) export const BLOCKSTACK_INC = 'gaia-hub' const DEFAULT_PROFILE_FILE_NAME = 'profile.json' +const DEFAULT_IDENTITY_SETTINGS_FILE_NAME = 'settings.json' function getProfileUploadLocation(identity: any, hubConfig: GaiaHubConfig) { if (identity.zoneFile) { @@ -23,6 +25,10 @@ function getProfileUploadLocation(identity: any, hubConfig: GaiaHubConfig) { } } +function getSettingsUploadLocation(hubConfig: GaiaHubConfig) { + return `${hubConfig.url_prefix}${hubConfig.address}/${DEFAULT_IDENTITY_SETTINGS_FILE_NAME}` +} + // aaron-debt: this should be moved into blockstack.js function canWriteUrl(url: string, hubConfig: GaiaHubConfig): ?string { const readPrefix = `${hubConfig.url_prefix}${hubConfig.address}/` @@ -116,3 +122,43 @@ export function uploadProfile( return uploadAttempt }) } + +export function uploadIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig, gaiaHubUrl: string}, + identityKeyPair: { key: string, keyID: string }, + settingsData: string +) { + const publicKey = identityKeyPair.keyID + const encryptedSettingsData = encryptContent(settingsData, { publicKey }) + return connectToGaiaHub(api.gaiaHubUrl, identityKeyPair.key).then(identityHubConfig => { + const urlToWrite = getSettingsUploadLocation(identityHubConfig) + return tryUpload( + urlToWrite, + encryptedSettingsData, + identityHubConfig, + 'application/json' + ) + }) +} + +export function fetchIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + ownerAddress: string, + identityKeyPair: { key: string } +) { + const privateKey = identityKeyPair.key + const hubConfig = api.gaiaHubConfig + const url = `${hubConfig.url_prefix}${ownerAddress}/${DEFAULT_IDENTITY_SETTINGS_FILE_NAME}` + return fetch(url) + .then(response => { + if (response.ok) { + return response.text() + .then(encryptedSettingsData => decryptContent(encryptedSettingsData, { privateKey })) + .then(decryptedSettingsData => JSON.parse(decryptedSettingsData)) + } else if (response.status == 404) { + return {} + } else { + return Promise.reject('Could not fetch identity settings') + } + }) +} diff --git a/app/js/profiles/DefaultProfilePage.js b/app/js/profiles/DefaultProfilePage.js index e7eb105bc..d29a4b697 100644 --- a/app/js/profiles/DefaultProfilePage.js +++ b/app/js/profiles/DefaultProfilePage.js @@ -66,7 +66,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return bindActionCreators( - Object.assign({}, IdentityActions, AccountActions), + Object.assign({}, AccountActions, IdentityActions), dispatch ) } @@ -79,6 +79,7 @@ export class DefaultProfilePage extends Component { createNewProfile: PropTypes.func.isRequired, updateProfile: PropTypes.func.isRequired, refreshIdentities: PropTypes.func.isRequired, + refreshAllIdentitySettings: PropTypes.func.isRequired, refreshSocialProofVerifications: PropTypes.func.isRequired, api: PropTypes.object.isRequired, identityAddresses: PropTypes.array.isRequired, @@ -112,6 +113,11 @@ export class DefaultProfilePage extends Component { componentWillMount() { logger.info('componentWillMount') this.props.refreshIdentities(this.props.api, this.props.identityAddresses) + this.props.refreshAllIdentitySettings( + this.props.api, + this.props.identityAddresses, + this.props.identityKeypairs + ) } componentWillReceiveProps(nextProps) { diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index fc238ff9a..c7e2025fe 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -440,6 +440,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { const identityAddresses = [] const identityKeypairs = [] + const identitySettings = [] // We pre-generate a number of identity addresses so that we // don't have to prompt the user for the password on each new profile @@ -455,6 +456,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { const identityKeyPair = deriveIdentityKeyPair(identityOwnerAddressNode) identityKeypairs.push(identityKeyPair) identityAddresses.push(identityKeyPair.address) + identitySettings.push({}) logger.debug(`createAccount: identity index: ${addressIndex}`) } @@ -463,6 +465,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } } diff --git a/test/profiles/DefaultProfilePage.test.js b/test/profiles/DefaultProfilePage.test.js index ba657c8bd..074a8753b 100644 --- a/test/profiles/DefaultProfilePage.test.js +++ b/test/profiles/DefaultProfilePage.test.js @@ -26,7 +26,8 @@ function setup(accounts = []) { encryptedBackupPhrase: 'onwards and upwards', setDefaultIdentity: () => {}, identityKeypairs: [], - storageConnected: false + storageConnected: false, + refreshAllIdentitySettings: () => {} } const wrapper = shallow()