Source

lib/util/http.js

// Dependencies
const util = require('util')
let request = util.promisify(require('postman-request'))

// Includes
const options = require('../options.js')
const settings = require('../../settings.json')
const cache = require('../cache')
const getHash = require('./getHash.js').func

// Args
exports.required = ['url']
exports.optional = ['options', 'ignoreLoginError']

// Define
request = request.defaults({
  forever: true,
  agentOptions: {
    maxSockets: Infinity
  },
  simple: false,
  gzip: true,
  timeout: settings.timeout
})

// Docs
/**
 * ✅ Send an http request to url with options.
 * @category Utility
 * @alias http
 * @param {string} url - The url to request to.
 * @param {object} options - The options to send with the request.
 * @param {boolean} ignoreLoginError - If any login errors should be ignored.
 * @returns {Promise<string>}
 * @example const noblox = require("noblox.js")
 * const body = await noblox.http("https://roblox.com/login", { method: "GET" })
**/

function http (url, opt) {
  if (opt && !opt.jar && Object.keys(opt).indexOf('jar') > -1) {
    opt.jar = options.jar
  }
  if (settings.session_only && opt && opt.jar) {
    if (!opt.headers) {
      opt.headers = {}
    }
    opt.headers.cookie = '.ROBLOSECURITY=' + opt.jar.session + ';'
    opt.headers['x-api-key'] = opt.jar.apiKey
    opt.jar = null
  }
  if (opt && opt.verification) {
    if (!opt.headers) {
      opt.headers = {}
    }
    const verify = '__RequestVerificationToken=' + opt.verification + ';'
    if (opt.headers.cookie) {
      opt.headers.cookie += verify
    } else {
      opt.headers.cookie = verify
    }
  }
  if (url.indexOf('http') !== 0) {
    url = 'https:' + url
  }
  return request(url, opt)
}

exports.func = function (args) {
  const opt = args.options || {}
  if (typeof opt.jar === 'string') {
    opt.jar = { session: opt.jar }
  }
  const jar = opt.jar
  let depth = args.depth || 0
  const full = opt.resolveWithFullResponse || false
  opt.resolveWithFullResponse = true
  const follow = opt.followRedirect === undefined || opt.followRedirect
  opt.followRedirect = function (res) {
    if (!args.ignoreLoginError && res.headers.location && (res.headers.location.startsWith('https://www.roblox.com/newlogin') || res.headers.location.startsWith('/Login/Default.aspx'))) {
      return false
    }
    return follow
  }
  return http(args.url, opt).then(function (res) {
    if (opt && opt.headers && opt.headers['X-CSRF-TOKEN']) {
      if (res.statusCode === 403) {
        let message

        try {
          message = typeof res.body === 'string' ? JSON.parse(res.body).message : res.body.message
        } catch (_) {
          // Roblox didn't send back a properly formed json object
        }

        if (message === 'XSRF Token Validation Failed' || message === 'Token Validation Failed') {
          depth++

          if (depth >= 3) {
            throw new Error('Tried ' + depth + ' times and could not refresh XCSRF token successfully')
          }

          const token = res.headers['x-csrf-token']

          if (token) {
            opt.headers['X-CSRF-TOKEN'] = token
            opt.jar = jar
            args.depth = depth + 1
            return exports.func(args)
          } else {
            throw new Error('Could not refresh X-CSRF-TOKEN')
          }
        }
      } else {
        if (depth > 0) {
          cache.add(options.cache, 'XCSRF', getHash({ jar }), opt.headers['X-CSRF-TOKEN'])
        }
      }
    }
    if (res.statusCode === 302 && !args.ignoreLoginError && res.headers.location && (res.headers.location.startsWith('https://www.roblox.com/newlogin') || res.headers.location.startsWith('/Login/Default.aspx'))) {
      throw new Error('You are not logged in')
    }
    return full ? res : res.body
  })
}