index.js

'use strict'

var Connection = require('./lib/connection')
var errors = require('./lib/errors')

if (!global.Promise) {
  throw new Error('Promise could not be found in this context. You must polyfill it to use this module.')
}

/**
 * @constructor
 * @description Your client to interact with the [Habitica API](https://habitica.com/apidoc/).
 *
 * @param {Object} options - The properties to configure the Habitica client
 * @param {String} [options.id] - The id of the user
 * @param {String} [options.apiToken] - The API token of the user
 * @param {String} [options.endpoint=https://habitica.com] - The endpoint to use
 * @param {String} [options.platform=Habitica-Node] - The name of your integration
 * @param {Function} [options.errorHandler] - A function to run when a request errors. The result of this function will be the argument passed to the `catch` in the request `Promise`
 *
 * @example
 * var Habitica = require('habitica')
 * var api = new Habitica({
 *   id: 'your-habitica.com-user-id',
 *   apiToken: 'your-habitica.com-api-token',
 *   endpoint: 'http://custom-url.com',
 *   platform: 'The Name of Your Integration'
 *   errorHandler: function (err) {
 *     // handle all errors from failed requests
 *   }
 * })
 * @example <caption>The id and apiToken parameters are not required and can be set later. The credentials will be automatically set when using the register and localLogin methods.</caption>
 * var Habitica = require('habitica')
 * var api = new Habitica()
 * @example <caption>A sample error handler</caption>
 * var Habitica = require('habitica')
 *
 * function sendNotification (style, message) {
 *   // logic for sending a notification to user
 * }
 *
 * var api = new Habitica({
 *   id: 'your-habitica.com-user-id',
 *   apiToken: 'your-habitica.com-api-token',
 *   errorHandler: function (err) {
 *     if (err instanceof Habitica.ApiError) {
 *       // likely a validation error from
 *       // the API request
 *       sendNotification('warning', err.messsage)
 *     } else if (err instanceof Habitica.UnknownConnectionError) {
 *       // either the Habitica API is down
 *       // or there is no internet connection
 *       sendNotification('danger', err.originalError.message)
 *     } else {
 *       // there is something wrong with your integration
 *       // such as a syntax error or other problem
 *       console.error(err)
 *     }
 *   }
 * })
 *
 * api.get('/tasks/id-that-does-not-exist').then(() => {
 *   // will never get here
 *   return api.get('/something-else')
 * }).then(() => {
 *   // will never get here
 * }).catch((err) => {
 *   // before this happens, the errorHandler gets run
 *   err // undefined because the errorHandler did not return anything
 *   // you could leave the catch off entirely since the
 *   // configured errorHandler does all the necessary work
 *   // to message back to the user
 * })
 */
function Habitica (options) {
  options = options || {}

  this._connection = new Connection(options)
}

/** @public
 *
 * @returns {Object} The options used to make the requests. The same as the values used {@link Habitica#setOptions|to set the options}
 */
Habitica.prototype.getOptions = function () {
  return this._connection.getOptions()
}

/** @public
 *
 * @param {Object} options - The properties to configure the Habitica client. If a property is not passed in, it will default to the value passed in on instantiation
 * @param {String} [options.id] - The id of the user
 * @param {String} [options.apiToken] - The API apiToken of the user
 * @param {String} [options.endpoint] - The endpoint to use
 * @param {String} [options.platform] - The name of your integration
 * @param {Function} [options.errorHandler] - A function to run when a request errors
 *
 * @example
 * api.setOptions({
 *   id: 'new-user-id',
 *   apiToken: 'new-api-token',
 *   endpoint: 'http://localhost:3000/',
 *   platform: 'Great-Habitica-Integration',
 *   errorHandler: yourErrorHandlerFunction
 * })
 */
Habitica.prototype.setOptions = function (creds) {
  this._connection.setOptions(creds)
}

/** @public
 *
 * @param {String} username - The username to register with
 * @param {String} email - The email to register with
 * @param {String} password - The password to register with
 *
 * @returns {Promise} A Promise that resolves the response from the register request
 *
 * @example <caption>The id and api token will be set automatically after a sucessful registration request</caption>
 * api.register('username', 'email', 'password').then((res) => {
 *   var user = res.data
 * }).catch((err) => {
 *   // handle registration errors
 * })
 */
Habitica.prototype.register = function (username, email, password) {
  return this.post('/user/auth/local/register', {
    username: username,
    email: email,
    password: password,
    confirmPassword: password
  }).then(function (res) {
    this.setOptions({
      id: res.data._id,
      apiToken: res.data.apiToken
    })

    return res
  }.bind(this))
}

/** @public
 *
 * @param {String} usernameOrEmail - The username or email to login with
 * @param {String} password - The password to login with
 *
 * @returns {Promise} A Promise that resolves the response from the login request
 * @example <caption>The id and api token will be set automatically after a sucessful login request</caption>
 * api.login('username or email','password').then((res) => {
 *   var creds = res.data
 *
 *   creds.id // the user's id
 *   creds.apiToken // the user's api token
 * }).catch((err) => {
 *   // handle login errors
 * })
 */
Habitica.prototype.localLogin = function (usernameEmail, password) {
  return this.post('/user/auth/local/login', {
    username: usernameEmail,
    password: password
  }).then(function (res) {
    this._connection.setOptions({
      id: res.data.id,
      apiToken: res.data.apiToken
    })

    return res
  }.bind(this))
}

/** @public
 *
 * @param {String} route - The Habitica API route to use
 * @param {Object} [query] - Query params to send along with the request
 *
 * @returns {Promise} A Promise that resolves the response from the GET request
 * @example <caption>Making a basic request</caption>
 * api.get('/user').then((res) => {
 *   var user = res.data
 *
 *   user.profile.name // the user's display name
 * })
 * @example <caption>A request with a query Object</caption>
 * api.get('/groups', {
 *   type: 'publicGuilds,privateGuilds'
 * }).then((res) => {
 *   var guilds = res.data
 *   var guild = guilds[0]
 *
 *   guild.name // the name of the group
 * })
 *
 * @example <caption>Handling errors</caption>
 * api.get('/tasks/non-existant-id').then((res) => {
 *   // will never get here
 * }).catch((err) => {
 *   err.message // 'Task not found'
 * })
 */
Habitica.prototype.get = function (path, query) {
  return this._connection.get(path, {
    query: query
  })
}

/** @public
 *
 * @param {String} route - The Habitica API route to use
 * @param {Object} [body] - The body to send along with the request
 * @param {Object} [query] - Query params to send along with the request
 *
 * @returns {Promise} A Promise that resolves the response from the POST request
 *
 * @example <caption>A request with a body</caption>
 * api.post('/tasks/user', {
 *   text: 'Task Name',
 *   notes: 'Task Notes',
 *   type: 'todo'
 * }).then((res) => {
 *   var task = res.data
 *
 *   task.text // 'Task Name'
 * })
 *
 * @example <caption>Handling errors</caption>
 * api.post('/groups', {
 *   type: 'party',
 *   name: 'My Party'
 * }).then((res) => {
 *   var party = res.data
 *
 *   party.name // 'My Party'
 * }).catch((err) => {
 *   // handle errors
 * })
 */
Habitica.prototype.post = function (path, body, query) {
  return this._connection.post(path, {
    send: body,
    query: query
  })
}

/** @public
 *
 * @param {String} route - The Habitica API route to use
 * @param {Object} [body] - The body to send along with the request
 * @param {Object} [query] - Query params to send along with the request
 *
 * @returns {Promise} A Promise that resolves the response from the PUT request
 *
 * @example <caption>A request with a body</caption>
 * api.put('/tasks/the-task-id', {
 *   text: 'New Task Name',
 *   notes: 'New Text Notes'
 * }).then((res) => {
 *   var task = res.data
 *
 *   task.text // 'New Task Name'
 * })
 *
 * @example <caption>Handling errors</caption>
 * api.put('/groups/the-group-id', {
 *   name: 'New Group Name'
 * }).then((res) => {
 *   var group = res.data
 *
 *   group.name // 'New Group Name'
 * }).catch((err) => {
 *   // handle errors
 * })
 */
Habitica.prototype.put = function (path, body, query) {
  return this._connection.put(path, {
    send: body,
    query: query
  })
}

/** @public
 *
 * @param {String} route - The Habitica API route to use
 * @param {Object} [body] - The body to send along with the request
 * @param {Object} [query] - Query params to send along with the request
 *
 * @returns {Promise} A Promise that resolves the response from the DELETE request
 *
 * @example <caption>A basic request</caption>
 * api.del('/tasks/the-task-id').then(() => {
 *   // The task has been deleted
 * })
 *
 * @example <caption>Handling errors</caption>
 * api.del('/groups/the-group-id').then(() => {
 *  // The group has been deleted
 * }).catch((err) => {
 *   // handle errors
 * })
 */
Habitica.prototype.del = function (path, body, query) {
  return this._connection.del(path, {
    send: body,
    query: query
  })
}

Habitica.ApiError = errors.HabiticaApiError
Habitica.UnknownConnectionError = errors.UnknownConnectionError

module.exports = Habitica