React Native Cloud Account Information

How can we get the information for a specific cloud account?

These code snippets demonstrate how the React Native application gets the cloud account information for a particular user and a particular cloud account. Note that this code will NOT succeed unless a valid Rosetta JWT is presented at the API endpoint along with this request. Also note that a cloud account is synonymous with a target site...the idea is that users are targeting the sites where their cloud accounts exist.

--

screens/SelectSite.js

--

import React from 'react';
import { FlatList, TouchableHighlight, View } from 'react-native';

import {
  Body,
  Button, 
  Container, 
  Content, 
  Footer,
  Header, 
  Item,
  Left, 
  Right, 
  Title, 
  Text,
} from 'native-base';
import { connect } from 'react-redux';

import {
  targetSiteSelectTargetAndPreloadSiteSpec,
} from '../redux/actions-targetSite';

function SiteItem({ siteSpec, onPressHandler }) {
  return (
    <TouchableHighlight
      key={siteSpec.siteId}
      onPress={() => onPressHandler(siteSpec)}
    >
      <View style={{ padding: 20 }}>
        <Text>{siteSpec.siteName}</Text>
      </View>
    </TouchableHighlight>
  );
}

function SelectSite(props) {
  const { navigation } = props;
  
  const handleNext = () => {
    navigation.navigate('CopyInfo');
  }

  const handlePrev = () => {
    navigation.navigate('SetPasscode');
  }

  const handleSelectSite = (siteSpec) => {
    props.targetSiteSelectTargetAndPreloadSiteSpec(siteSpec);
  }

  const isSiteSelected = () => {
    return props.selectedSiteSpec != null && props.selectedSiteSpec.siteId != null;
  }

  // Problem if FlatList enclosed within Content and NativeBase List really slow
  return (
      <Container>
        <Header>
          <Body>
            <Title>{ props.selectedSiteSpec.siteName || 'N/A' }</Title>
          </Body>
        </Header>

        <FlatList
          data={props.siteSpecs}
          renderItem={ ({ item }) => <SiteItem siteSpec={item} onPressHandler={handleSelectSite} /> }
          keyExtractor={ item => item.siteId }
        />

        <Footer>
            <Item>
              <Button onPress={() => handlePrev()} >
                <Text>Prev</Text>
              </Button>
            </Item>
            <Item>
              <Button disabled={!isSiteSelected()} onPress={() => handleNext()} >
                <Text>Next</Text>
              </Button>
            </Item>
        </Footer>
      </Container>
  );
}

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    selectedSiteSpec: state.targetSiteReducer.selectedTargetSiteSpec,
    siteSpecs: state.targetSiteReducer.potentialTargetSiteSpecs,
  }
}

const mapDispatchToProps = {
  targetSiteSelectTargetAndPreloadSiteSpec,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SelectSite)

--

redux/actions-targetSite.js

--

import {
  TARGET_SITE_SELECT_TARGET,
} from './actionTypes'

import { targetPasswordLoadSpec } from './actions-common'

export function targetSiteSelectTarget(selectedTargetSiteSpec) {
  return { type: TARGET_SITE_SELECT_TARGET, selectedTargetSiteSpec }
}

export function targetSiteSelectTargetAndPreloadSiteSpec(selectedTargetSiteSpec) {
  return (dispatch) => {
    dispatch(targetSiteSelectTarget(selectedTargetSiteSpec));
    dispatch(targetPasswordLoadSpec(selectedTargetSiteSpec.siteId));
  };
}

--

redux/actions-common.js

--

import axios from 'axios'
import { isTokenExpired } from './utility'

import {
  SERVER_TOKEN_SET_TOKEN_EXPIRED,
  TARGET_PASSWORD_LOAD_SPEC_STARTED,
  TARGET_PASSWORD_LOAD_SPEC_SUCCESS,
  TARGET_PASSWORD_LOAD_SPEC_ERROR,
} from './actionTypes'

import { cryptoCreateHash } from './utility'

// Common

export function commonIsTranslateTokenExpired(state) {
  return isTokenExpired(state.serverTokenReducer.serverTokenExpirationSeconds);
}

export function serverTokenSetTokenExpired() {
  return { type: SERVER_TOKEN_SET_TOKEN_EXPIRED }
}

// Target Password related

export function targetPasswordLoadSpec(siteId) {
  return (dispatch, getState) => {
    // guard clause - translate token already expired
    if (commonIsTranslateTokenExpired(getState())) {
      dispatch(serverTokenSetTokenExpired());
      return;
    }
    
    dispatch(targetPasswordLoadSpecStarted());
    const axiosClient = commonCreateAxiosClientWithTranslateToken(getState());
    axiosClient
      .get("/api/target-sites/" + siteId)
      .then(res => { dispatch(targetPasswordLoadSpecProcess(res.data)); })
      .catch(err => { dispatch(targetPasswordLoadSpecError(err.response)); });
  };
}

// Helper Functions

function commonCreateAxiosClientWithTranslateToken(state) {
  const axiosClient = axios.create();
  axiosClient.defaults.baseURL = state.serverTokenReducer.serverBaseUri;
  axiosClient.defaults.headers.common['Authorization'] = "Bearer " + state.serverTokenReducer.serverTokenValue;
  axiosClient.defaults.headers.common['X-Device-Id'] = state.serverTokenReducer.deviceId;
  return axiosClient;
}

async function targetPasswordCreatePasswordHash(passwordValue, selectedTargetSiteInfo) {
  // guard clause - invalid input
  if (selectedTargetSiteInfo == null || selectedTargetSiteInfo.siteSalt == null) {
    return "";
  }

  return await cryptoCreateHash(passwordValue + selectedTargetSiteInfo.siteSalt);
}

function targetPasswordLoadSpecError(errorResponse) {
  return { type: TARGET_PASSWORD_LOAD_SPEC_ERROR, errorResponse }
}

function targetPasswordLoadSpecProcess(selectedTargetSiteInfo) {
  return (dispatch, getState) => {
    let transientPasswordValue = getState().sourcePasswordReducer.transientPasswordValue;
    targetPasswordCreatePasswordHash(transientPasswordValue, selectedTargetSiteInfo)
      .then(newHashValue => {
        let newState = {
          selectedTargetSiteInfo: selectedTargetSiteInfo,
          targetHashValue: newHashValue
        }
        dispatch(targetPasswordLoadSpecSuccess(newState))
      });
  };
}

function targetPasswordLoadSpecStarted() {
  return { type: TARGET_PASSWORD_LOAD_SPEC_STARTED }
}

function targetPasswordLoadSpecSuccess(newState) {
  return { type: TARGET_PASSWORD_LOAD_SPEC_SUCCESS, newState }
}

--

redux/reducers-targetPassword.js

--

import {
  TARGET_PASSWORD_LOAD_SPEC_STARTED,
  TARGET_PASSWORD_LOAD_SPEC_SUCCESS,
  TARGET_PASSWORD_LOAD_SPEC_ERROR,
} from './actionTypes'

import { commonGetErrorMessage } from './reducers-common'

function targetPasswordAdjustPasswordHash(passwordHash, selectedTargetSiteInfo) {
  // make adjustment to password hash, according to rules for target site
  return transformedPasswordHash;
}

const targetPasswordInitialState = {
  selectedTargetSiteError: '',
  selectedTargetSiteInfo: {
    siteRules: {
      startIndex: 0,
      stopIndex: 10,
      forceCapitalIndex: 0,
      forceNumberIndex: 1,
      forceSpecialIndex: 2,
      forceSpecialCharacter: '%'
    },
    siteUserid: ''
  },
  targetAdjustedHashValue: '',
  targetHashValue: '',
  targetSaltValue: '',
  updateSiteInfo: {
    siteRules: {}
  }
}

export function targetPasswordReducer(state = targetPasswordInitialState, action) {
  let errorMessage = "";
  switch (action.type) {
    case TARGET_PASSWORD_LOAD_SPEC_ERROR:
      errorMessage = commonGetErrorMessage(action.errorResponse);
      return { 
        ...state, 
        selectedTargetSiteError: errorMessage
      }
    case TARGET_PASSWORD_LOAD_SPEC_STARTED:
      return state
    case TARGET_PASSWORD_LOAD_SPEC_SUCCESS:
      let loadTargetHashValue = action.newState.targetHashValue;
      let loadTargetSiteInfo = action.newState.selectedTargetSiteInfo;
      let loadAdjustedHashValue = targetPasswordAdjustPasswordHash(loadTargetHashValue, loadTargetSiteInfo);
      return { 
        ...state, 
        selectedTargetSiteInfo: loadTargetSiteInfo,
        targetAdjustedHashValue: loadAdjustedHashValue,
        targetHashValue: loadTargetHashValue,
        targetSaltValue: loadTargetSiteInfo.siteSalt,
        updateSiteInfo: loadTargetSiteInfo
      }
    default:
      return state
  }
}

--