createUserProfile = (dispatch, firebase, userData, profile) => {
  const {
    _: { config }
  } = firebase
  if (!config.userProfile || (!firebase.database && !firebase.firestore)) {
    return Promise.resolve(userData)
  }
  // use profileFactory if it exists in config
  if (typeof config.profileFactory === 'function') {
    // catch errors in user provided profileFactory function
    try {
      profile = config.profileFactory(userData, profile, firebase) // eslint-disable-line no-param-reassign
    } catch (err) {
      /* eslint-disable no-console */
      console.error(
        'Error occurred within profileFactory function:',
        err.message || err
      )
      /* eslint-enable no-console */
      return Promise.reject(err)
    }
  }

  // Check/Write profile using Firestore
  if (config.useFirestoreForProfile) {
    // Check for user's profile at userProfile path if provided
    return firebase
      .firestore()
      .collection(config.userProfile)
      .doc(userData.uid || userData.user.uid)
      .get()
      .then((profileSnap) => {
        // Return if config for updating profile is not enabled and profile exists
        if (!config.updateProfileOnLogin && profileSnap.exists) {
          return profileSnap.data()
        }
        let newProfile = profile

        // If the user did supply a profileFactory, we should use the result of it for the new Profile
        if (!newProfile) {
          // Convert to JSON format (to prevent issue of writing invalid type to Firestore)
          const userDataObject = userData.uid
            ? userData.toJSON
              ? userData.toJSON()
              : userData
            : userData.user.toJSON
            ? userData.user.toJSON()
            : userData.user
          // Remove unnecessary auth params (configurable) and preserve types of timestamps
          newProfile = {
            ...omit(userDataObject, config.keysToRemoveFromAuth),
            avatarUrl: userDataObject.photoURL // match profile pattern used for RTDB
          }
        }

        // Convert custom object type within Provider data to a normal object
        if (Array.isArray(newProfile.providerData)) {
          newProfile.providerData = newProfile.providerData.map(
            (providerDataItem) =>
              pick(providerDataItem, config.keysToPreserveFromProviderData)
          )
        }

        // Create/Update the profile
        return profileSnap.ref
          .set(newProfile, { merge: true })
          .then(() => newProfile)
      })
      .catch((err) => {
        // Error reading user profile
        dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: err })
        return Promise.reject(err)
      })
  }

  // Check/Write profile using Firebase RTDB
  return firebase
    .database()
    .ref()
    .child(
      `${config.userProfile}/${
        userData.user ? userData.user.uid : userData.uid
      }`
    )
    .once('value')
    .then(
      (profileSnap) =>
        // update profile only if doesn't exist or if set by config
        !config.updateProfileOnLogin && profileSnap.val() !== null
          ? profileSnap.val()
          : profileSnap.ref.update(profile).then(() => profile) // Update the profile
    )
    .catch((err) => {
      // Error reading user profile
      dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: err })
      if (typeof config.onProfileWriteError === 'function') {
        config.onProfileWriteError(err, firebase)
      }
      return Promise.reject(err)
    })
}
Create user profile if it does not already exist.
`updateProfileOnLogin: false` can be passed to config to disable updating.
Profile factory is applied if it exists and is a function.
@param {Function} dispatch - Action dispatch function
@param {object} firebase - Internal firebase object
@param {object} userData - User data object (response from authenticating)
@param {object} profile - Profile data to place in new profile
@returns {Promise} Resolves after creating user profile
@private