"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fetchAuth0Token = fetchAuth0Token;
exports.fetchOrInitializeUser = fetchOrInitializeUser;
exports.createOrUpdateUser = createOrUpdateUser;
exports.deleteUser = deleteUser;
exports.resendVerificationEmail = resendVerificationEmail;
exports.fetchMonitoredTrips = fetchMonitoredTrips;
exports.createOrUpdateUserMonitoredTrip = createOrUpdateUserMonitoredTrip;
exports.togglePauseTrip = togglePauseTrip;
exports.toggleSnoozeTrip = toggleSnoozeTrip;
exports.confirmAndDeleteUserMonitoredTrip = confirmAndDeleteUserMonitoredTrip;
exports.requestPhoneVerificationSms = requestPhoneVerificationSms;
exports.verifyPhoneNumber = verifyPhoneNumber;
exports.checkItineraryExistence = checkItineraryExistence;
exports.planNewTripFromMonitoredTrip = planNewTripFromMonitoredTrip;
exports.saveUserPlace = saveUserPlace;
exports.deleteUserPlace = deleteUserPlace;
exports.clearItineraryExistence = exports.setPathBeforeSignIn = void 0;

var _clone = _interopRequireDefault(require("clone"));

var _moment = _interopRequireDefault(require("moment"));

var _query = require("@opentripplanner/core-utils/lib/query");

var _time = require("@opentripplanner/core-utils/lib/time");

var _qs = _interopRequireDefault(require("qs"));

var _reduxActions = require("redux-actions");

var _constants = require("../util/constants");

var _middleware = require("../util/middleware");

var _ui = require("../util/ui");

var _user = require("../util/user");

var _ui2 = require("./ui");

var _form = require("./form");

var _api = require("./api");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// Middleware API paths.
const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip';
const API_OTPUSER_PATH = '/api/secure/user';
const API_OTPUSER_VERIFY_SMS_SUBPATH = '/verify_sms';
const setAccessToken = (0, _reduxActions.createAction)('SET_ACCESS_TOKEN');
const setCurrentUser = (0, _reduxActions.createAction)('SET_CURRENT_USER');
const setCurrentUserMonitoredTrips = (0, _reduxActions.createAction)('SET_CURRENT_USER_MONITORED_TRIPS');
const setLastPhoneSmsRequest = (0, _reduxActions.createAction)('SET_LAST_PHONE_SMS_REQUEST');
const setPathBeforeSignIn = (0, _reduxActions.createAction)('SET_PATH_BEFORE_SIGNIN');
exports.setPathBeforeSignIn = setPathBeforeSignIn;
const clearItineraryExistence = (0, _reduxActions.createAction)('CLEAR_ITINERARY_EXISTENCE');
exports.clearItineraryExistence = clearItineraryExistence;
const setitineraryExistence = (0, _reduxActions.createAction)('SET_ITINERARY_EXISTENCE');

function createNewUser(auth0User) {
  return {
    auth0UserId: auth0User.sub,
    email: auth0User.email,
    hasConsentedToTerms: false,
    // User must agree to terms.
    notificationChannel: 'email',
    phoneNumber: '',
    savedLocations: [],
    storeTripHistory: false // User must opt in.

  };
}
/**
 * Extracts accessToken, loggedInUser from the redux state,
 * and apiBaseUrl, apiKey from the middleware configuration.
 * If the middleware configuration does not exist, throws an error.
 */


function getMiddlewareVariables(state) {
  const {
    otp,
    user
  } = state;
  const {
    otp_middleware: otpMiddleware = null
  } = otp.config.persistence;

  if (otpMiddleware) {
    const {
      accessToken,
      loggedInUser
    } = user;
    const {
      apiBaseUrl,
      apiKey
    } = otpMiddleware;
    return {
      accessToken,
      apiBaseUrl,
      apiKey,
      loggedInUser
    };
  } else {
    throw new Error('This action requires config.yml#persistence#otp_middleware to be defined.');
  }
}
/**
 * Attempts to fetch the auth0 access token and store it in the redux state, under state.user.
 * @param auth0 The auth0 context used to obtain the access token for subsequent middleware fetches.
 */


function fetchAuth0Token(auth0) {
  return async function (dispatch, getState) {
    try {
      const accessToken = await auth0.getAccessTokenSilently();
      dispatch(setAccessToken(accessToken));
    } catch (error) {
      // TODO: improve UI if there is an error.
      alert('Error obtaining an authorization token.');
    }
  };
}
/**
 * Updates the redux state with the provided user data, including
 * placing the Home and Work locations at the beginning of the list
 * of saved places for rendering in several UI components.
 *
 * Also, fetches monitored trips if requested, i.e. when
 * - initializing the user state with an existing persisted user, or
 * - POST-ing a user for the first time.
 */


function setUser(user, fetchTrips) {
  return function (dispatch, getState) {
    (0, _user.positionHomeAndWorkFirst)(user);
    dispatch(setCurrentUser(user));

    if (fetchTrips) {
      dispatch(fetchMonitoredTrips());
    }
  };
}
/**
 * Attempts to fetch user preferences (or set initial values if the user is being created)
 * into the redux state, under state.user.
 * @param auth0User If provided, the auth0.user object used to initialize the default user object (with email and auth0 id).
 */


function fetchOrInitializeUser(auth0User) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/fromtoken`;
    const {
      data: user,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey); // Beware! On AWS API gateway, if a user is not found in the middleware
    // (e.g. they just created their Auth0 password but have not completed the account setup form yet),
    // the call above will return, for example:
    // {
    //    status: 'success',
    //    data: {
    //      "result": "ERR",
    //      "message": "No user with id=000000 found.",
    //      "code": 404,
    //      "detail": null
    //    }
    // }
    //
    // The same call to a middleware instance that is not behind an API gateway
    // will return:
    // {
    //    status: 'error',
    //    message: 'Error get-ing user...'
    // }
    // TODO: Improve AWS response.

    const isNewAccount = status === 'error' || user && user.result === 'ERR';
    const userData = isNewAccount ? createNewUser(auth0User) : user; // Set user in redux state.
    // (This sorts saved places, and, for existing users, fetches trips.)

    dispatch(setUser(userData, !isNewAccount));
  };
}
/**
 * Updates (or creates) a user entry in the middleware,
 * then, if that was successful, updates the redux state with that user.
 * @param userData the user entry to persist.
 * @param silentOnSuccess true to suppress the confirmation if the operation is successful (e.g. immediately after user accepts the terms).
 */


function createOrUpdateUser(userData, silentOnSuccess = false) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey,
      loggedInUser
    } = getMiddlewareVariables(getState());
    const {
      id
    } = userData; // Middleware ID, NOT auth0 (or similar) id.

    let method, requestUrl; // Before persisting, filter out entries from userData.savedLocations with blank addresses.

    userData.savedLocations = userData.savedLocations.filter(({
      address
    }) => !(0, _ui.isBlank)(address)); // Determine URL and method to use.

    const isCreatingUser = (0, _user.isNewUser)(loggedInUser);

    if (isCreatingUser) {
      requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}`;
      method = 'POST';
    } else {
      requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${id}`;
      method = 'PUT';
    }

    const {
      data: returnedUser,
      message,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, method, {
      body: JSON.stringify(userData)
    }); // TODO: improve the UI feedback messages for this.

    if (status === 'success' && returnedUser) {
      if (!silentOnSuccess) {
        alert('Your preferences have been saved.');
      } // Update application state with the user entry as saved
      // (as returned) by the middleware.
      // (This sorts saved places, and, for existing users, fetches trips.)


      dispatch(setUser(returnedUser, isCreatingUser));
    } else {
      alert(`An error was encountered:\n${JSON.stringify(message)}`);
    }
  };
}
/**
 * Deletes a user (and their corresponding trips, requests, etc.) from the
 * middleware database.
 * @param userData the user account to delete
 * @param auth0 auth0 object (gives access to logout function)
 */


function deleteUser(userData, auth0) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const {
      id
    } = userData; // Middleware ID, NOT auth0 (or similar) id.

    const {
      data: deletedUser,
      message,
      status
    } = await (0, _middleware.secureFetch)(`${apiBaseUrl}${API_OTPUSER_PATH}/${id}`, accessToken, apiKey, 'DELETE'); // TODO: improve the UI feedback messages for this.

    if (status === 'success' && deletedUser) {
      alert(`Your user account (${userData.email}) has been deleted.`); // Log out user and route them to the home page.

      auth0.logout({
        returnTo: _constants.URL_ROOT
      });
    } else {
      alert(`An error was encountered:\n${JSON.stringify(message)}`);
    }
  };
}
/**
 * Requests the verification email for the new user to be resent.
 */


function resendVerificationEmail() {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState()); // TODO: add any throttling.

    const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/verification-email`;
    const {
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey); // TODO: improve the UI feedback messages for this.

    if (status === 'success') {
      alert('The email verification message has been resent.');
    }
  };
}
/**
 * Fetches the saved/monitored trips for a user.
 * We use the accessToken to fetch the data regardless of
 * whether the process to populate state.user is completed or not.
 */


function fetchMonitoredTrips() {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}`;
    const {
      data: trips,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, 'GET');

    if (status === 'success') {
      dispatch(setCurrentUserMonitoredTrips(trips.data));
    }
  };
}
/**
 * Updates a logged-in user's monitored trip,
 * then, if that was successful, alerts (optional)
 * and refreshes the redux monitoredTrips with the updated trip.
 */


function createOrUpdateUserMonitoredTrip(tripData, isNew, silentOnSuccess, noRedirect) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const {
      id
    } = tripData;
    let method, requestUrl; // Determine URL and method to use.

    if (isNew) {
      requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}`;
      method = 'POST';
    } else {
      requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/${id}`;
      method = 'PUT';
    }

    const {
      data,
      message,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, method, {
      body: JSON.stringify(tripData)
    }); // TODO: improve the UI feedback messages for this.

    if (status === 'success' && data) {
      if (!silentOnSuccess) {
        alert('Your preferences have been saved.');
      } // Reload user's monitored trips after add/update.


      await dispatch(fetchMonitoredTrips());
      if (noRedirect) return; // Finally, navigate to the saved trips page.

      dispatch((0, _ui2.routeTo)(_constants.TRIPS_PATH));
    } else {
      alert(`An error was encountered:\n${JSON.stringify(message)}`);
    }
  };
}
/**
 * Toggles the isActive status of a monitored trip
 */


function togglePauseTrip(trip) {
  return function (dispatch, getState) {
    const clonedTrip = (0, _clone.default)(trip);
    clonedTrip.isActive = !clonedTrip.isActive; // Silent update of existing trip.

    dispatch(createOrUpdateUserMonitoredTrip(clonedTrip, false, true, true));
  };
}
/**
 * Toggles the snoozed status of a monitored trip
 */


function toggleSnoozeTrip(trip) {
  return function (dispatch, getState) {
    const newTrip = (0, _clone.default)(trip);
    newTrip.snoozed = !newTrip.snoozed; // Silent update of existing trip.

    dispatch(createOrUpdateUserMonitoredTrip(newTrip, false, true, true));
  };
}
/**
 * Deletes a logged-in user's monitored trip,
 * then, if that was successful, refreshes the redux monitoredTrips state.
 */


function confirmAndDeleteUserMonitoredTrip(tripId) {
  return async function (dispatch, getState) {
    if (!confirm('Would you like to remove this trip?')) return;
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/${tripId}`;
    const {
      message,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, 'DELETE');

    if (status === 'success') {
      // Reload user's monitored trips after deletion.
      dispatch(fetchMonitoredTrips());
    } else {
      alert(`An error was encountered:\n${JSON.stringify(message)}`);
    }
  };
}
/**
 * Requests a verification code via SMS for the logged-in user.
 */


function requestPhoneVerificationSms(newPhoneNumber) {
  return async function (dispatch, getState) {
    const state = getState();
    const {
      number,
      timestamp
    } = state.user.lastPhoneSmsRequest;
    const now = new Date(); // Request a new verification code if we are requesting a different number.
    // or enough time has elapsed since the last request (1 minute?).
    // TODO: Should throttling be handled in the middleware?

    if (number !== newPhoneNumber || now - timestamp >= 60000) {
      const {
        accessToken,
        apiBaseUrl,
        apiKey,
        loggedInUser
      } = getMiddlewareVariables(state);
      const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFY_SMS_SUBPATH}/${encodeURIComponent(newPhoneNumber)}`;
      const {
        message,
        status
      } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, 'GET');
      dispatch(setLastPhoneSmsRequest({
        number: newPhoneNumber,
        status,
        timestamp: now
      }));

      if (status === 'success') {
        // Refetch user and update application state with new phone number and verification status.
        dispatch(fetchOrInitializeUser());
      } else {
        alert(`An error was encountered:\n${JSON.stringify(message)}`);
      }
    } else {
      // Alert user if they have been throttled.
      // TODO: improve this alert.
      alert('A verification SMS was sent to the indicated phone number less than a minute ago. Please try again in a few moments.');
    }
  };
}
/**
 * Validate the phone number verification code for the logged-in user.
 */


function verifyPhoneNumber(code) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey,
      loggedInUser
    } = getMiddlewareVariables(getState());
    const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFY_SMS_SUBPATH}/${code}`;
    const {
      data,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, 'POST'); // If the check is successful, status in the returned data will be "approved".

    if (status === 'success' && data) {
      if (data.status === 'approved') {
        // Refetch user and update application state with new phone number and verification status.
        dispatch(fetchOrInitializeUser());
      } else {
        // Otherwise, the user entered a wrong/incorrect code.
        alert('The code you entered is invalid. Please try again.');
      }
    } else {
      // This happens when an error occurs on backend side, especially
      // when the code has expired (or was cancelled by Twilio after too many attempts).
      alert(`Your phone could not be verified. Perhaps the code you entered has expired. Please request a new code and try again.`);
    }
  };
}
/**
 * Check itinerary existence for the given monitored trip.
 */


function checkItineraryExistence(trip) {
  return async function (dispatch, getState) {
    const {
      accessToken,
      apiBaseUrl,
      apiKey
    } = getMiddlewareVariables(getState());
    const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/checkitinerary`; // Empty state before performing the checks.

    dispatch(clearItineraryExistence());
    const {
      data,
      status
    } = await (0, _middleware.secureFetch)(requestUrl, accessToken, apiKey, 'POST', {
      body: JSON.stringify(trip)
    });

    if (status === 'success' && data) {
      dispatch(setitineraryExistence(data));
    } else {
      alert('Error checking whether your selected trip is possible.');
    }
  };
}
/**
 * Plans a new trip for the current date given the query parameters in the given
 * monitored trip
 */


function planNewTripFromMonitoredTrip(monitoredTrip) {
  return function (dispatch, getState) {
    // update query params in store
    const newQuery = (0, _query.planParamsToQuery)(_qs.default.parse(monitoredTrip.queryParams));
    newQuery.date = (0, _moment.default)().format(_time.OTP_API_DATE_FORMAT);
    dispatch((0, _form.setQueryParam)(newQuery));
    dispatch((0, _ui2.routeTo)('/')); // This prevents some kind of race condition whose origin I can't figure
    // out. Unless this is called after redux catches up with routing to the '/'
    // path, then the old path will be used and the screen won't change.
    // Therefore, this timeout occurs so that the view of the homepage has time
    // to render itself.
    // FIXME: remove hack

    setTimeout(() => {
      dispatch((0, _api.routingQuery)());
    }, 300);
  };
}
/**
 * Saves the given place data at the specified index for the logged-in user.
 * Note: places with blank addresses will not appear in persistence.
 */


function saveUserPlace(placeToSave, placeIndex) {
  return function (dispatch, getState) {
    const {
      loggedInUser
    } = getState().user;

    if (placeIndex === 'new') {
      loggedInUser.savedLocations.push(placeToSave);
    } else {
      loggedInUser.savedLocations[placeIndex] = placeToSave;
    }

    dispatch(createOrUpdateUser(loggedInUser, true));
  };
}
/**
 * Delete the place data at the specified index for the logged-in user.
 */


function deleteUserPlace(placeIndex) {
  return function (dispatch, getState) {
    if (!confirm('Would you like to remove this place?')) return;
    const {
      loggedInUser
    } = getState().user;
    loggedInUser.savedLocations.splice(placeIndex, 1);
    dispatch(createOrUpdateUser(loggedInUser, true));
  };
}

//# sourceMappingURL=user.js