mojang.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /**
  2. * Mojang
  3. *
  4. * This module serves as a minimal wrapper for Mojang's REST api.
  5. *
  6. * @module mojang
  7. */
  8. // Requirements
  9. const request = require('request')
  10. // Constants
  11. const minecraftAgent = {
  12. name: 'Minecraft',
  13. version: 1
  14. }
  15. const authpath = 'https://authserver.mojang.com'
  16. const statuses = [
  17. {
  18. service: 'sessionserver.mojang.com',
  19. status: 'grey',
  20. name: 'Multiplayer Session Service',
  21. essential: true
  22. },
  23. {
  24. service: 'authserver.mojang.com',
  25. status: 'grey',
  26. name: 'Authentication Service',
  27. essential: true
  28. },
  29. {
  30. service: 'textures.minecraft.net',
  31. status: 'grey',
  32. name: 'Minecraft Skins',
  33. essential: false
  34. },
  35. {
  36. service: 'api.mojang.com',
  37. status: 'grey',
  38. name: 'Public API',
  39. essential: false
  40. },
  41. {
  42. service: 'minecraft.net',
  43. status: 'grey',
  44. name: 'Minecraft.net',
  45. essential: false
  46. },
  47. {
  48. service: 'account.mojang.com',
  49. status: 'grey',
  50. name: 'Mojang Accounts Website',
  51. essential: false
  52. }
  53. ]
  54. // Functions
  55. /**
  56. * Converts a Mojang status color to a hex value. Valid statuses
  57. * are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
  58. * to our project which represents an unknown status.
  59. *
  60. * @param {string} status A valid status code.
  61. * @returns {string} The hex color of the status code.
  62. */
  63. exports.statusToHex = function(status){
  64. switch(status.toLowerCase()){
  65. case 'green':
  66. return '#a5c325'
  67. case 'yellow':
  68. return '#eac918'
  69. case 'red':
  70. return '#c32625'
  71. case 'grey':
  72. default:
  73. return '#848484'
  74. }
  75. }
  76. /**
  77. * Retrieves the status of Mojang's services.
  78. * The response is condensed into a single object. Each service is
  79. * a key, where the value is an object containing a status and name
  80. * property.
  81. *
  82. * @see http://wiki.vg/Mojang_API#API_Status
  83. */
  84. exports.status = function(){
  85. return new Promise((resolve, reject) => {
  86. request.get('https://status.mojang.com/check',
  87. {
  88. json: true,
  89. timeout: 2500
  90. },
  91. function(error, response, body){
  92. if(error || response.statusCode !== 200){
  93. console.warn('Unable to retrieve Mojang status.')
  94. console.debug('Error while retrieving Mojang statuses:', error)
  95. //reject(error || response.statusCode)
  96. for(let i=0; i<statuses.length; i++){
  97. statuses[i].status = 'grey'
  98. }
  99. resolve(statuses)
  100. } else {
  101. for(let i=0; i<body.length; i++){
  102. const key = Object.keys(body[i])[0]
  103. inner:
  104. for(let j=0; j<statuses.length; j++){
  105. if(statuses[j].service === key) {
  106. statuses[j].status = body[i][key]
  107. break inner
  108. }
  109. }
  110. }
  111. resolve(statuses)
  112. }
  113. })
  114. })
  115. }
  116. /**
  117. * Authenticate a user with their Mojang credentials.
  118. *
  119. * @param {string} username The user's username, this is often an email.
  120. * @param {string} password The user's password.
  121. * @param {string} clientToken The launcher's Client Token.
  122. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  123. * @param {Object} agent Optional. Provided by default. Adds user info to the response.
  124. *
  125. * @see http://wiki.vg/Authentication#Authenticate
  126. */
  127. exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
  128. return new Promise((resolve, reject) => {
  129. request.post(authpath + '/authenticate',
  130. {
  131. json: true,
  132. body: {
  133. agent,
  134. username,
  135. password,
  136. clientToken,
  137. requestUser
  138. }
  139. },
  140. function(error, response, body){
  141. if(error){
  142. console.error('Error during authentication.', error)
  143. reject(error)
  144. } else {
  145. if(response.statusCode === 200){
  146. resolve(body)
  147. } else {
  148. reject(body || {code: 'ENOTFOUND'})
  149. }
  150. }
  151. })
  152. })
  153. }
  154. /**
  155. * Validate an access token. This should always be done before launching.
  156. * The client token should match the one used to create the access token.
  157. *
  158. * @param {string} accessToken The access token to validate.
  159. * @param {string} clientToken The launcher's client token.
  160. *
  161. * @see http://wiki.vg/Authentication#Validate
  162. */
  163. exports.validate = function(accessToken, clientToken){
  164. return new Promise((resolve, reject) => {
  165. request.post(authpath + '/validate',
  166. {
  167. json: true,
  168. body: {
  169. accessToken,
  170. clientToken
  171. }
  172. },
  173. function(error, response, body){
  174. if(error){
  175. console.error('Error during validation.', error)
  176. reject(error)
  177. } else {
  178. if(response.statusCode === 403){
  179. resolve(false)
  180. } else {
  181. // 204 if valid
  182. resolve(true)
  183. }
  184. }
  185. })
  186. })
  187. }
  188. /**
  189. * Invalidates an access token. The clientToken must match the
  190. * token used to create the provided accessToken.
  191. *
  192. * @param {string} accessToken The access token to invalidate.
  193. * @param {string} clientToken The launcher's client token.
  194. *
  195. * @see http://wiki.vg/Authentication#Invalidate
  196. */
  197. exports.invalidate = function(accessToken, clientToken){
  198. return new Promise((resolve, reject) => {
  199. request.post(authpath + '/invalidate',
  200. {
  201. json: true,
  202. body: {
  203. accessToken,
  204. clientToken
  205. }
  206. },
  207. function(error, response, body){
  208. if(error){
  209. console.error('Error during invalidation.', error)
  210. reject(error)
  211. } else {
  212. if(response.statusCode === 204){
  213. resolve()
  214. } else {
  215. reject(body)
  216. }
  217. }
  218. })
  219. })
  220. }
  221. /**
  222. * Refresh a user's authentication. This should be used to keep a user logged
  223. * in without asking them for their credentials again. A new access token will
  224. * be generated using a recent invalid access token. See Wiki for more info.
  225. *
  226. * @param {string} accessToken The old access token.
  227. * @param {string} clientToken The launcher's client token.
  228. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  229. *
  230. * @see http://wiki.vg/Authentication#Refresh
  231. */
  232. exports.refresh = function(accessToken, clientToken, requestUser = true){
  233. return new Promise((resolve, reject) => {
  234. request.post(authpath + '/refresh',
  235. {
  236. json: true,
  237. body: {
  238. accessToken,
  239. clientToken,
  240. requestUser
  241. }
  242. },
  243. function(error, response, body){
  244. if(error){
  245. console.error('Error during refresh.', error)
  246. reject(error)
  247. } else {
  248. if(response.statusCode === 200){
  249. resolve(body)
  250. } else {
  251. reject(body)
  252. }
  253. }
  254. })
  255. })
  256. }