mojang.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. },
  90. function(error, response, body){
  91. if(error || response.statusCode !== 200){
  92. console.warn('Unable to retrieve Mojang status.')
  93. console.debug('Error while retrieving Mojang statuses:', error)
  94. reject(error || response.statusCode)
  95. } else {
  96. for(let i=0; i<body.length; i++){
  97. const key = Object.keys(body[i])[0]
  98. inner:
  99. for(let j=0; j<statuses.length; j++){
  100. if(statuses[j].service === key) {
  101. statuses[j].status = body[i][key]
  102. break inner
  103. }
  104. }
  105. }
  106. resolve(statuses)
  107. }
  108. })
  109. })
  110. }
  111. /**
  112. * Authenticate a user with their Mojang credentials.
  113. *
  114. * @param {string} username The user's username, this is often an email.
  115. * @param {string} password The user's password.
  116. * @param {string} clientToken The launcher's Client Token.
  117. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  118. * @param {Object} agent Optional. Provided by default. Adds user info to the response.
  119. *
  120. * @see http://wiki.vg/Authentication#Authenticate
  121. */
  122. exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
  123. return new Promise((resolve, reject) => {
  124. request.post(authpath + '/authenticate',
  125. {
  126. json: true,
  127. body: {
  128. agent,
  129. username,
  130. password,
  131. clientToken,
  132. requestUser
  133. }
  134. },
  135. function(error, response, body){
  136. if(error){
  137. console.error('Error during authentication.', error)
  138. reject(error)
  139. } else {
  140. if(response.statusCode === 200){
  141. resolve(body)
  142. } else {
  143. reject(body || {code: 'ENOTFOUND'})
  144. }
  145. }
  146. })
  147. })
  148. }
  149. /**
  150. * Validate an access token. This should always be done before launching.
  151. * The client token should match the one used to create the access token.
  152. *
  153. * @param {string} accessToken The access token to validate.
  154. * @param {string} clientToken The launcher's client token.
  155. *
  156. * @see http://wiki.vg/Authentication#Validate
  157. */
  158. exports.validate = function(accessToken, clientToken){
  159. return new Promise((resolve, reject) => {
  160. request.post(authpath + '/validate',
  161. {
  162. json: true,
  163. body: {
  164. accessToken,
  165. clientToken
  166. }
  167. },
  168. function(error, response, body){
  169. if(error){
  170. console.error('Error during validation.', error)
  171. reject(error)
  172. } else {
  173. if(response.statusCode === 403){
  174. resolve(false)
  175. } else {
  176. // 204 if valid
  177. resolve(true)
  178. }
  179. }
  180. })
  181. })
  182. }
  183. /**
  184. * Invalidates an access token. The clientToken must match the
  185. * token used to create the provided accessToken.
  186. *
  187. * @param {string} accessToken The access token to invalidate.
  188. * @param {string} clientToken The launcher's client token.
  189. *
  190. * @see http://wiki.vg/Authentication#Invalidate
  191. */
  192. exports.invalidate = function(accessToken, clientToken){
  193. return new Promise((resolve, reject) => {
  194. request.post(authpath + '/invalidate',
  195. {
  196. json: true,
  197. body: {
  198. accessToken,
  199. clientToken
  200. }
  201. },
  202. function(error, response, body){
  203. if(error){
  204. console.error('Error during invalidation.', error)
  205. reject(error)
  206. } else {
  207. if(response.statusCode === 204){
  208. resolve()
  209. } else {
  210. reject(body)
  211. }
  212. }
  213. })
  214. })
  215. }
  216. /**
  217. * Refresh a user's authentication. This should be used to keep a user logged
  218. * in without asking them for their credentials again. A new access token will
  219. * be generated using a recent invalid access token. See Wiki for more info.
  220. *
  221. * @param {string} accessToken The old access token.
  222. * @param {string} clientToken The launcher's client token.
  223. * @param {boolean} requestUser Optional. Adds user object to the reponse.
  224. *
  225. * @see http://wiki.vg/Authentication#Refresh
  226. */
  227. exports.refresh = function(accessToken, clientToken, requestUser = true){
  228. return new Promise((resolve, reject) => {
  229. request.post(authpath + '/refresh',
  230. {
  231. json: true,
  232. body: {
  233. accessToken,
  234. clientToken,
  235. requestUser
  236. }
  237. },
  238. function(error, response, body){
  239. if(error){
  240. console.error('Error during refresh.', error)
  241. reject(error)
  242. } else {
  243. if(response.statusCode === 200){
  244. resolve(body)
  245. } else {
  246. reject(body)
  247. }
  248. }
  249. })
  250. })
  251. }