"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.UserManagerInstance = exports.ANONYMOUS_USERNAME = void 0;

function _isEmpty() {
  const data = _interopRequireDefault(require("lodash/isEmpty"));

  _isEmpty = function () {
    return data;
  };

  return data;
}

function _camelCase() {
  const data = _interopRequireDefault(require("lodash/camelCase"));

  _camelCase = function () {
    return data;
  };

  return data;
}

function _snakeCase() {
  const data = _interopRequireDefault(require("lodash/snakeCase"));

  _snakeCase = function () {
    return data;
  };

  return data;
}

function _ApiV() {
  const data = _interopRequireDefault(require("./ApiV2"));

  _ApiV = function () {
    return data;
  };

  return data;
}

function Analytics() {
  const data = _interopRequireWildcard(require("./Analytics"));

  Analytics = function () {
    return data;
  };

  return data;
}

function _Config() {
  const data = _interopRequireDefault(require("./Config"));

  _Config = function () {
    return data;
  };

  return data;
}

function _XDLError() {
  const data = _interopRequireDefault(require("./XDLError"));

  _XDLError = function () {
    return data;
  };

  return data;
}

function _Logger() {
  const data = _interopRequireDefault(require("./Logger"));

  _Logger = function () {
    return data;
  };

  return data;
}

function _UserSettings() {
  const data = _interopRequireDefault(require("./UserSettings"));

  _UserSettings = function () {
    return data;
  };

  return data;
}

function _Utils() {
  const data = require("./Utils");

  _Utils = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

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

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

const ANONYMOUS_USERNAME = 'anonymous';
exports.ANONYMOUS_USERNAME = ANONYMOUS_USERNAME;

class UserManagerInstance {
  constructor() {
    _defineProperty(this, "_currentUser", null);

    _defineProperty(this, "_getSessionLock", new (_Utils().Semaphore)());

    _defineProperty(this, "_interactiveAuthenticationCallbackAsync", void 0);
  }

  static getGlobalInstance() {
    if (!__globalInstance) {
      __globalInstance = new UserManagerInstance();
    }

    return __globalInstance;
  }

  initialize() {
    this._currentUser = null;
    this._getSessionLock = new (_Utils().Semaphore)();
  }
  /**
   * Logs in a user for a given login type.
   *
   * Valid login types are:
   *  - "user-pass": Username and password authentication
   *
   * If the login type is "user-pass", we directly make the request to www
   * to login a user.
   */


  async loginAsync(loginType, loginArgs) {
    if (loginType === 'user-pass') {
      if (!loginArgs) {
        throw new Error(`The 'user-pass' login type requires a username and password.`);
      }

      const apiAnonymous = _ApiV().default.clientForUser();

      const loginResp = await apiAnonymous.postAsync('auth/loginAsync', {
        username: loginArgs.username,
        password: loginArgs.password
      });

      if (loginResp.error) {
        throw new (_XDLError().default)('INVALID_USERNAME_PASSWORD', loginResp['error_description']);
      }

      return this._getProfileAsync({
        currentConnection: 'Username-Password-Authentication',
        sessionSecret: loginResp.sessionSecret
      });
    } else {
      throw new Error(`Invalid login type provided. Must be 'user-pass'.`);
    }
  }

  async registerAsync(userData, user = null) {
    if (!user) {
      user = await this.getCurrentUserAsync();
    }

    if (user) {
      await this.logoutAsync();
      user = null;
    }

    try {
      // Create or update the profile
      let registeredUser = await this.createOrUpdateUserAsync({
        connection: 'Username-Password-Authentication',
        // Always create/update username password
        email: userData.email,
        givenName: userData.givenName,
        familyName: userData.familyName,
        username: userData.username,
        password: userData.password
      });
      registeredUser = await this.loginAsync('user-pass', {
        username: userData.username,
        password: userData.password
      });
      return registeredUser;
    } catch (e) {
      console.error(e);
      throw new (_XDLError().default)('REGISTRATION_ERROR', 'Error registering user: ' + e.message);
    }
  }
  /**
   * Ensure user is logged in and has a valid token.
   *
   * If there are any issues with the login, this method throws.
   */


  async ensureLoggedInAsync() {
    if (_Config().default.offline) {
      throw new (_XDLError().default)('NETWORK_REQUIRED', 'Can\'t verify user without network access');
    }

    let user = await this.getCurrentUserAsync();

    if (!user && this._interactiveAuthenticationCallbackAsync) {
      user = await this._interactiveAuthenticationCallbackAsync();
    }

    if (!user) {
      throw new (_XDLError().default)('NOT_LOGGED_IN', 'Not logged in');
    }

    return user;
  }

  setInteractiveAuthenticationCallback(callback) {
    this._interactiveAuthenticationCallbackAsync = callback;
  }

  async _readUserData() {
    let auth = await _UserSettings().default.getAsync('auth', null);

    if ((0, _isEmpty().default)(auth)) {
      // XXX(ville):
      // We sometimes read an empty string from ~/.expo/state.json,
      // even though it has valid credentials in it.
      // We don't know why.
      // An empty string can't be parsed as JSON, so an empty default object is returned.
      // In this case, retrying usually helps.
      auth = await _UserSettings().default.getAsync('auth', null);
    }

    if (typeof auth === 'undefined') {
      return null;
    }

    return auth;
  }
  /**
   * Get the current user based on the available token.
   * If there is no current token, returns null.
   */


  async getCurrentUserAsync() {
    await this._getSessionLock.acquire();

    try {
      // If user is cached and there is a sessionSecret, return the user
      if (this._currentUser && this._currentUser.sessionSecret) {
        return this._currentUser;
      }

      if (_Config().default.offline) {
        return null;
      }

      const data = await this._readUserData(); // No session, no current user. Need to login

      if (!data || !data.sessionSecret) {
        return null;
      }

      try {
        return await this._getProfileAsync({
          currentConnection: data.currentConnection,
          sessionSecret: data.sessionSecret
        });
      } catch (e) {
        _Logger().default.global.warn('Fetching the user profile failed');

        _Logger().default.global.warn(e);

        return null;
      }
    } finally {
      this._getSessionLock.release();
    }
  }

  async getCurrentUsernameAsync() {
    const data = await this._readUserData();

    if (!data || !data.username) {
      return null;
    }

    return data.username;
  }

  async getSessionAsync() {
    const data = await this._readUserData();

    if (!data || !data.sessionSecret) {
      return null;
    }

    return {
      sessionSecret: data.sessionSecret
    };
  }
  /**
   * Create or update a user.
   */


  async createOrUpdateUserAsync(userData) {
    let currentUser = this._currentUser;

    if (!currentUser) {
      // attempt to get the current user
      currentUser = await this.getCurrentUserAsync();
    }

    const api = _ApiV().default.clientForUser(currentUser);

    const {
      user: updatedUser
    } = await api.postAsync('auth/createOrUpdateUser', {
      userData: _prepareAuth0Profile(userData)
    });
    this._currentUser = { ...this._currentUser,
      ..._parseAuth0Profile(updatedUser),
      kind: 'user'
    };
    return this._currentUser;
  }
  /**
   * Logout
   */


  async logoutAsync() {
    if (this._currentUser) {
      Analytics().logEvent('Logout', {
        username: this._currentUser.username
      });
    }

    this._currentUser = null; // Delete saved auth info

    await _UserSettings().default.deleteKeyAsync('auth');
  }
  /**
   * Forgot Password
   */


  async forgotPasswordAsync(usernameOrEmail) {
    const apiAnonymous = _ApiV().default.clientForUser();

    return apiAnonymous.postAsync('auth/forgotPasswordAsync', {
      usernameOrEmail
    });
  }
  /**
   * Get profile given token data. Errors if token is not valid or if no
   * user profile is returned.
   *
   * This method is called by all public authentication methods of `UserManager`
   * except `logoutAsync`. Therefore, we use this method as a way to:
   *  - update the UserSettings store with the current token and user id
   *  - update UserManager._currentUser
   *  - Fire login analytics events
   *
   * Also updates UserManager._currentUser.
   *
   * @private
   */


  async _getProfileAsync({
    currentConnection,
    sessionSecret
  }) {
    let user;

    let api = _ApiV().default.clientForUser({
      sessionSecret
    });

    user = await api.postAsync('auth/userProfileAsync');

    if (!user) {
      throw new Error('Unable to fetch user.');
    }

    user = { ..._parseAuth0Profile(user),
      kind: 'user',
      currentConnection,
      sessionSecret
    };
    await _UserSettings().default.setAsync('auth', {
      userId: user.userId,
      username: user.username,
      currentConnection,
      sessionSecret
    }); // If no currentUser, or currentUser.id differs from profiles
    // user id, that means we have a new login

    if ((!this._currentUser || this._currentUser.userId !== user.userId) && user.username && user.username !== '') {
      Analytics().logEvent('Login', {
        userId: user.userId,
        currentConnection: user.currentConnection,
        username: user.username
      });
      Analytics().setUserProperties(user.username, {
        userId: user.userId,
        currentConnection: user.currentConnection,
        username: user.username
      });
    }

    this._currentUser = user;
    return user;
  }

}

exports.UserManagerInstance = UserManagerInstance;

let __globalInstance;

var _default = UserManagerInstance.getGlobalInstance();
/** Private Methods **/


exports.default = _default;

function _parseAuth0Profile(rawProfile) {
  if (!rawProfile || typeof rawProfile !== 'object') {
    return rawProfile;
  }

  return Object.keys(rawProfile).reduce((p, key) => {
    p[(0, _camelCase().default)(key)] = _parseAuth0Profile(rawProfile[key]);
    return p;
  }, {});
}

function _prepareAuth0Profile(niceProfile) {
  if (typeof niceProfile !== 'object') {
    return niceProfile;
  }

  return Object.keys(niceProfile).reduce((p, key) => {
    p[(0, _snakeCase().default)(key)] = _prepareAuth0Profile(niceProfile[key]);
    return p;
  }, {});
}
//# sourceMappingURL=__sourcemaps__/User.js.map