Source: index.js

/* 
    Andrea Sponziello - (c) Tiledesk.com
*/

const request = require('request');
const { v4: uuidv4 } = require('uuid');

/**
 * This class is a NodeJS stub for Tiledesk's REST APIs
 */
class TiledeskClient {

  static DEFAULT_API_ENDPOINT = "https://api.tiledesk.com/v2";
  static ASSIGNED_STATUS = 200;
  static UNASSIGNED_STATUS = 100;

  /**
   * Constructor for TiledeskClient object
   *
   * @example
   * const { TiledeskClient } = require('tiledesk-client');
   * const tdclient = new TiledeskClient({APIKEY: 'THE_API_KEY'});
   * const tdclient = new TiledeskClient({APIKEY: 'THE_API_KEY', APIURL: 'SELF_HOSTED_INSTANCE_ENDPOINT'});
   * 
   *
   * @param {Object} options JSON configuration.
   * @param {string} options.APIKEY Mandatory. Tiledesk APIKEY
   * @param {string} options.APIURL Optional. Tiledesk server API endpoint
   * @param {string} options.projectId Optional. Tiledesk projectId. Will be used in each call on project's APIs.
   * @param {string} options.token Optional. Tiledesk authentication token. Will be used in each call on project's APIs.
   * @param {boolean} options.log Optional. If true HTTP requests are logged
   * 
   */
  constructor(options) {
    if (!options.APIKEY) {
      throw new Error('APIKEY can NOT be empty.');
    }
    else {
      this.APIKEY = options.APIKEY;
    }
    // if (!options.token) {
    //   throw new Error('token can NOT be empty.');
    // }

    if (options && options.APIURL) {
      this.APIURL = options.APIURL
    }
    else {
      this.APIURL = TiledeskClient.DEFAULT_API_ENDPOINT;
    }

    if (options && options.projectId) {
      this.projectId = options.projectId;
    }

    if (options && options.token) {
      this.token = options.token;
    }

    this.log = false;
    if (options && options.log) {
      this.log = options.log;
    }
  }

  static fixToken(token) {
    if (token.startsWith('JWT ')) {
      return token
    }
    else {
      return 'JWT ' + token
    }
  }

  /** Returns a new request ID for the specified Project.<br>
   * A request's ID has the format:<br>
   * <br>
   * <i>support-group-PROJECT_ID-UNIQUE_ID</i><br>
   * <br>
   * <i>UNIQUE_ID</i> MUST be UNIQUE in your Project. <b>This method always returns an <i>UUID</i> for the <i>UNIQUE_ID</i> component</b>.
   * 
   * @param {string} projectId - The project ID for the new request.
  */
  static newRequestId(projectId) {
    const request_id = 'support-group-' + projectId + '-' + uuidv4().replace(/-/g, '');
    return request_id;
  }

  createProject(projectId, token, callback) {
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/projects/${projectId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    };
    TiledeskClient.myrequest(HTTPREQUEST,
    function(err, response, resbody) {
      if (response.statusCode === 200) {
        if (callback) {
         callback(null, resbody)
        }
      }
      else if (callback) {
        callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
      }

      // if (response.statusCode == 200) {
      //   callback(null, resbody)
      // }
      // else {
      //   const error_msg = "getProjectSettings. Status code: " + response.statusCode
      //   callback(error_msg, null)
      // }
    }, this. log);
  }

  /**
   * Returns the project's JSON configuration<br>
   * <a href='https://developer.tiledesk.com/apis/rest-api/projects#get-the-project-detail' target='_blank'>REST API</a>
   * 
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  getProjectSettings(callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/projects/${projectId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    };
    TiledeskClient.myrequest(HTTPREQUEST,
    function(err, response, resbody) {
      if (response.statusCode === 200) {
        if (callback) {
         callback(null, resbody)
        }
      }
      else if (callback) {
        callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
      }
    }, this.log);
  }

  /**
   * Updates the request's properties.<br>
   * <a href='https://developer.tiledesk.com/apis/rest-api/requests#update-a-request-by-request_id' target='_blank'>REST API</a>
   * 
   * @param {string} requestId - The request ID
   * @param {Object} properties - The request properties to update.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateRequestProperties(requestId, properties, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    var URL = `${this.APIURL}/${projectId}/requests/${requestId}`
    data = properties;
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: data,
      method: 'PATCH'
      };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
           callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Updates the request's attributes.<br>
   * <a href='https://developer.tiledesk.com/apis/rest-api/requests#update-the-request-attributes' target='_blank'>REST API</a>
   * 
   * @param {string} requestId - The request ID
   * @param {Object} attributes - The request attributes to update.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateRequestAttributes(requestId, attributes, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    var URL = `${this.APIURL}/${projectId}/requests/${requestId}/attributes`
    var data = attributes;
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: data,
      method: 'PATCH'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
           callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Returns a project's User (aka Teammate, is a User invited on a project, with additional properties and a spcific project-userId)
   * @param {string} user_id - The Teammate ID. Is the specific ID for this user on this project
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  getProjectUser(user_id, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/project_users/users/${user_id}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Updates the authenticated Teammate's (projectUser). The teammate must be invited to the specified project for the update operation taking success.<br>
   * <a href='https://developer.tiledesk.com/apis/rest-api/team#update-the-current-logged-teammate' target='_blank'>REST API</a>
   * 
   * @param {Object} properties - The properties to update. Only the provided properties will be updated, the other properties will stay unchanged.
   * @param {string} properties.role - The teammate role. Permitted values: 'admin', 'agent'.
   * @param {boolean} properties.user_available - The teammate availability. 'true' for available, 'false' for unavailable.
   * @param {number} properties.max_served_chat - The number of concurrent chats the teammate can take at once.
   * @param {Object} properties.attributes - The teammate custom attributes.
   * @param {Object} properties.settings - The teammate settings.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateProjectUserCurrentlyLoggedIn(properties, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/project_users/`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: properties,
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Updates the Teammate's (projectUser) by ProjectUser's ID. It requires admin role.
   * @param {string} projectUserId - The teammate ID.
   * @param {Object} properties - The properties to update. Only the provided properties will be updated, the other properties will stay unchanged.<br>
   * <b>role {string}</b> - The teammate role. Permitted values: 'admin', 'agent'.
   * <br><b>user_available {boolean}</b> - The teammate availability. 'true' for available, 'false' for unavailable.
   * <br><b>max_served_chat {number}</b> - The number of concurrent chats the teammate can take at once.
   * <br><b>attributes {Object}</b> - The teammate custom attributes.
   * <br><b>settings {Object}</b> - The teammate settings.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
   updateProjectUser(projectUserId, properties, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/project_users/${projectUserId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: properties,
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Only updates the available status for the specified Teammate. It requires admin role.
   * @param {string} projectUserId - The teammate ID.
   * @param {boolean} userAvailable - The teammate availability. 'true' for available, 'false' for unavailable.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateProjectUserAvailable(projectUserId, userAvailable, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/project_users/${projectUserId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: {
        user_available: userAvailable
      },
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (callback) {
        //   callback(err, resbody)
        // }
      }, this.log
    );
  }

  /**
   * Only updates the attributes for the specified Teammate. It requires admin role.
   * @param {string} projectUserId - The teammate ID.
   * @param {Object} attributes - The teammate custom attributes.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateProjectUserAttributes(projectUserId, attributes, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/project_users/${projectUserId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: {
        attributes: attributes
      },
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /* DEPRECATED, use getAllRequests */
  getRequests(limit, status, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    // direction = 1 => oldest must be served first
    // const URL = `${this.API_ENDPOINT}/${projectId}/requests?status=${status}&limit=${limit}&direction=1`
    let url = new URL(`${this.APIURL}/${projectId}/requests`)
    url.searchParams.append("status", status);
    url.searchParams.append("limit", limit);
    url.searchParams.append("direction", 1);
    if (options && options.additional_params) {
      for (let key in options.additional_params) {
        url.searchParams.append(key, options.additional_params[key]);
      }
    }
    // console.log("URL", url.href);
    const HTTPREQUEST = {
      url: url.href,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    }
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (resbody && resbody.requests) {
        //   if (callback) {
        //     callback(err, resbody.requests)
        //   }
        // }
        // else {
        //   // throw
        //   console.log("Error getting requests. Error:", err, " URL", URL, " token:", jwt_token, " Body:", resbody)
        // }

      }, this.log
    );
  }

  /**
   * @typedef queryParams
   * @type {object}
   * @property {string} sortField - what field to sort the results by. Default field is 'createdAt'
   * @property {string} direction - sort direction: 1 (asc) or -1 (desc). Return the results in ascending (1) or descending (-1) order. Defaults to desc (-1)
   * @property {number} page - What page of results to fetch. Defaults to first page.
   * @property {number} limit - Specifies the maximum number of results to be returned. Default is 40 rows
   * @property {string} full_text - Executes a fulltext search query
   * @property {string} status - Filters by request status. Values: 100 for unserved requests, 200 for served requests, 1000 for closed requests, "all" to retrieve all statuses. Default value is status < 1000 so it returns all the opened requests.
   * @property {string} dept_id - Filters by department's ID
   * @property {string} lead - Filters by lead's ID
   * @property {array} participant - Filters by participants (agent or bot)
   */

  /**
   * Query project's requests.
   * @param {queryParams} queryParams - The query parameters.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  getAllRequests(queryParams, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    if (queryParams == null) {
      queryParams = {}
    }
    const jwt_token = TiledeskClient.fixToken(token)
    // direction = 1 => oldest must be served first
    // const URL = `${this.API_ENDPOINT}/${projectId}/requests?status=${status}&limit=${limit}&direction=1`
    let url = new URL(`${this.APIURL}/${projectId}/requests`);
    for (const [key, value] of Object.entries(queryParams)) {
      url.searchParams.append(key, value);
    }
    // url.searchParams.append("status", status);
    // url.searchParams.append("limit", limit);
    // url.searchParams.append("direction", 1);
    // if (options && options.additional_params) {
    //   for (let key in options.additional_params) {
    //     url.searchParams.append(key, options.additional_params[key]);
    //   }
    // }
    // console.log("URL", url.href);
    const HTTPREQUEST = {
      url: url.href,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    }
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (resbody && resbody.requests) {
        //   if (callback) {
        //     callback(err, resbody.requests)
        //   }
        // }
        // else {
        //   // throw
        //   console.log("Error getting requests. Error:", err, " URL", URL, " token:", jwt_token, " Body:", resbody)
        // }

      }, this.log
    );
  }

  /**
   * Gets a reuqest by ID.
   * @param {string} requestId - The request's ID.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  getRequestById(requestId, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/requests/${requestId}`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: true,
      method: 'GET'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * Updates the request's partecipants.<br>
   * <a href='https://developer.tiledesk.com/apis/rest-api/requests#set-the-request-participants' target='_blank'>REST API</a>
   * @param {queryParams} requestId - The request's ID.
   * @param {array} participants - The participants (agent or bot) identifiers array
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  updateRequestParticipants(requestId, participants, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const URL = `${this.APIURL}/${projectId}/requests/${requestId}/participants`
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: participants,
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
          callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  getWidgetSettings(callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const HTTPREQUEST = {
      url: `${this.APIURL}/${projectId}/widgets`,
      method: 'GET',
      json: true
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  openNow(callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const url = `${this.APIURL}/projects/${projectId}/isopen`
    const HTTPREQUEST = {
      url: url,
      headers: {
       'Content-Type' : 'application/json'
       //  'Authorization': jwt_token
      },
      method: 'GET',
      json: true
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
            if (callback) {
              callback(null, resbody)
            }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  static getErr(err, request, response, resbody) {
    let res_err = {}
    res_err.tiledesk_err = resbody;
    res_err.http_err = err;
    res_err.http_request = request;
    return res_err;
  }
  
  anonymousAuthentication(projectId, callback) {
    const HTTPREQUEST = {
      url: `${this.APIURL}/auth/signinAnonymously`,
      headers: {
        'Content-Type' : 'application/json'
      },
      json: {
        "id_project": projectId
      },
      method: 'POST'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        // resbody example:
        // {
        //   "success": true,
        //   "token": "JWT eyJ...",
        //   "user": {
        //       "firstname": "Guest",
        //       "id": "20bb30db-b677-...",
        //       "fullName": "Guest "
        //   }
        // }
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (callback) {
        //   callback(err, response, resbody)
        // }
      }, this.log
    );
  }

  customAuthentication(token, callback) {
    const jwt_token = TiledeskClient.fixToken(token)
    const HTTPREQUEST = {
      url: `${this.APIURL}/auth/signinWithCustomToken`,
      headers: {
        'Authorization' : jwt_token
      },
      json: true,
      method: 'POST'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (callback) {
        //   callback(err, response, resbody)
        // }
      }, this.log
    );
  }

  authEmailPassword(email, password, callback) {
    const HTTPREQUEST = {
      url: `${this.APIURL}/auth/signin`,
      headers: {
        'Content-Type' : 'application/json'
      },
      json: {
        email: email,
        password: password
      },
      method: 'POST'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }

        // if (callback) {
        //   callback(err, response, resbody)
        // }
      }, this.log
    );
  }

  /**
   * Sends a message to a support conversation.<br>
   * <a href='' target='_blank'>REST API</a>
   * 
   * @param {string} requestId - The request's ID.
   * @param {chatMessage} message - The chat21's message JSON object.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  sendSupportMessage(requestId, message, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const url = `${this.APIURL}/${projectId}/requests/${requestId}/messages`;
    const HTTPREQUEST = {
      url: url,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: message,
      method: 'POST'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  /**
   * The Chat21 message format. More on <a href='https://developer.tiledesk.com/widget/advanced/widget-json-protocol' target='_blank'>messages format (Review this link)</a>.
   * @typedef chatMessage
   * @type {Object}
   * @property {string} senderFullname - The sender full name
   * @property {string} recipient - The message recipiet's ID
   * @property {number} text - The message's text
   * @property {number} type - The message type. Allowed types are 'text' (default), 'image', 'frame'
   * @property {Object} metadata - The message's metadata. Some type as 'image' or 'frame' need metadata.
   * @property {Object} attributes - Custom attributes attacched to this message.
   */

  /**
   * Sends a message to a direct/group conversation.<br>
   * <a href='' target='_blank'>REST API</a>
   * 
   * @param {chatMessage} message - The chat21's message JSON object.
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   */
  sendChatMessage(message, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const url = `${this.APIURL}/${projectId}/messages`;
    const HTTPREQUEST = {
      url: url,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: message,
      method: 'POST'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (err) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
        else if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  fireEvent(event, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token)
    const HTTPREQUEST = {
      url: `${this.APIURL}/${projectId}/events`,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: event,
      method: 'POST'
   };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
          if (response.statusCode === 200) {
            if (callback) {
              callback(null, resbody)
            }
          }
          else if (callback) {
            callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
          }
      }, this.log
    );
  }
  
  // migrated from TiledeskChatbotUtil
  
  updateLeadEmailFullname(lead_id, email, fullname, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token);
    // if (!this.lead_id) {
    //   throw new Error('options.lead_id can NOT be empty.');
    // }
    const HTTPREQUEST = {
      url: `${this.APIURL}/${projectId}/leads/${lead_id}`, // this.conversation.lead._id
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: {
        email: email,
        fullname: fullname
      },
      method: 'PUT'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  updateRequest(request_id, properties, attributes, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    const jwt_token = TiledeskClient.fixToken(token);
    let URL = `${this.APIURL}/${projectId}/requests/${request_id}/attributes`
    let data = attributes
    if (properties) {
      URL = `${this.APIURL}/${projectId}/requests/${request_id}/`
      data = properties
    }
    
    const HTTPREQUEST = {
      url: URL,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: data,
      method: 'PATCH'
    };
    TiledeskClient.myrequest(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

/**
 * This callback type is called `resultCallback` and is provided as a return value by each API call.
 *
 * @callback resultCallback
 * @param {Object} result - the response body
 * @param {Object} error - the error if some occurs, otherwise null
 */

  /**
   * Updates the Request department
   * @param {string} requestId - The request ID
   * @param {string} depId - The new department ID
   * @param {resultCallback} callback - The callback that handles the response.
   * @param {Object} options - Optional configuration.
   * @param {string} options.token - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.projectId - The token for this request. Overrides instance token (if) provided in constructor.
   * @param {string} options.nobot - Optional. Defaults to <i>false</i>. If true ignores (if set) the bot in the Department.
   */
  updateRequestDepartment(requestId, depId, callback, options) {
    let token;
    if (options && options.token) {
      token = options.token;
    }
    else if (this.token) {
      token = this.token;
    }
    else {
      throw new Error('token can NOT be null.');
    }
    let projectId;
    if (options && options.projectId) {
      projectId = options.projectId;
    }
    else if (this.projectId) {
      projectId = this.projectId;
    }
    else {
      throw new Error('projectId can NOT be null.');
    }
    let nobot_option_defined = false;
    let nobot = false;
    if (options && options.nobot) {
      nobot = options.nobot;
      nobot_option_defined = true;
    }
    let data = {
      departmentid: depId
    }
    if (nobot_option_defined) {
      data['nobot'] = nobot;
    }
    const jwt_token = TiledeskClient.fixToken(token);
    const HTTPREQUEST = {
      url: `${this.APIURL}/${projectId}/requests/${requestId}/departments`,
      headers: {
        'Content-Type' : 'application/json',
        'Authorization': jwt_token
      },
      json: data,
      method: 'PUT'
    };
    request(
      HTTPREQUEST,
      function(err, response, resbody) {
        if (response.statusCode === 200) {
          if (callback) {
            callback(null, resbody)
          }
        }
        else if (callback) {
          callback(TiledeskClient.getErr(err, HTTPREQUEST, response, resbody), null);
        }
      }, this.log
    );
  }

  static myrequest(options, callback, log) {
    if (log) {
      console.log("API:", options.url);
    }
    request(
      {
        url: options.url,
        headers: options.headers,
        json: options.json,
        method: options.method
      },
      function(err, res, resbody) {
        if (log) {
          console.log("** For url:", options.url);
          console.log("** Options:", options);
          console.log("** Err:", err);
          console.log("** Response headers:\n", res.headers);
          console.log("** Response body:\n", res.body);
        }
        if (callback) {
          callback(err, res, resbody);
        }
      }
    );
  }

}

module.exports = { TiledeskClient };