commit
34f4bdf1e2
@ -613,8 +613,7 @@ body, button {
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
border: none;
|
||||
padding: 15px 5px;
|
||||
margin: 10px 0px;
|
||||
margin: 15px 0px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
right: -20px;
|
||||
@ -689,6 +688,21 @@ body, button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.circle-loader-big {
|
||||
margin:auto;
|
||||
border: 5px solid rgba(255, 255, 255, 0.5);
|
||||
border-left-color: #ffffff;
|
||||
animation-name: loader-spin;
|
||||
animation-duration: 1s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
position: relative;
|
||||
display: block;
|
||||
align-self: center;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.load-complete {
|
||||
animation: none;
|
||||
border-color: #ffffff;
|
||||
@ -859,6 +873,29 @@ body, button {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
#loginMSButton {
|
||||
background: none;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
#loginMSButton:disabled {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#loginMSButton:hover,
|
||||
#loginMSButton:focus {
|
||||
text-shadow: 0px 0px 20px #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#loginMSButton:active {
|
||||
color: #c7c7c7;
|
||||
text-shadow: 0px 0px 20px #c7c7c7;
|
||||
}
|
||||
|
||||
/*
|
||||
#login_filter {
|
||||
height: calc(100% - 22px);
|
||||
|
@ -12,9 +12,66 @@
|
||||
const ConfigManager = require('./configmanager')
|
||||
const LoggerUtil = require('./loggerutil')
|
||||
const Mojang = require('./mojang')
|
||||
const Microsoft = require('./microsoft')
|
||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
||||
|
||||
async function validateSelectedMojang() {
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async function validateSelectedMicrosoft() {
|
||||
try {
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const now = new Date().getTime()
|
||||
const MCExpiresAt = Date.parse(current.expiresAt)
|
||||
const MCExpired = now > MCExpiresAt
|
||||
|
||||
if(MCExpired) {
|
||||
const MSExpiresAt = Date.parse(current.microsoft.expires_at)
|
||||
const MSExpired = now > MSExpiresAt
|
||||
|
||||
if (MSExpired) {
|
||||
const newAccessToken = await Microsoft.refreshAccessToken(current.microsoft.refresh_token)
|
||||
const newMCAccessToken = await Microsoft.authMinecraft(newAccessToken.access_token)
|
||||
ConfigManager.updateAuthAccount(current.uuid, newMCAccessToken.access_token, newAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
return true
|
||||
}
|
||||
const newMCAccessToken = await Microsoft.authMinecraft(current.microsoft.access_token)
|
||||
ConfigManager.updateAuthAccount(current.uuid, newMCAccessToken.access_token, current.microsoft.access_token, current.microsoft.expires_at, newMCAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Exports
|
||||
// Functions
|
||||
|
||||
/**
|
||||
@ -55,6 +112,11 @@ exports.addAccount = async function(username, password){
|
||||
exports.removeAccount = async function(uuid){
|
||||
try {
|
||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
||||
if(authAcc.type === 'microsoft'){
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
return Promise.resolve()
|
||||
}
|
||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
@ -78,22 +140,35 @@ exports.validateSelected = async function(){
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
try{
|
||||
if (ConfigManager.getSelectedAccount() === 'microsoft') {
|
||||
const validate = await validateSelectedMicrosoft()
|
||||
return validate
|
||||
} else {
|
||||
const validate = await validateSelectedMojang()
|
||||
return validate
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else return true
|
||||
}
|
||||
|
||||
exports.addMSAccount = async authCode => {
|
||||
try {
|
||||
const accessToken = await Microsoft.getAccessToken(authCode)
|
||||
const MCAccessToken = await Microsoft.authMinecraft(accessToken.access_token)
|
||||
const minecraftBuyed = await Microsoft.checkMCStore(MCAccessToken.access_token)
|
||||
if(!minecraftBuyed)
|
||||
return Promise.reject({
|
||||
message: 'You didn\'t buy Minecraft! Please use another Microsoft account or buy Minecraft.'
|
||||
})
|
||||
const MCProfile = await Microsoft.getMCProfile(MCAccessToken.access_token)
|
||||
const ret = ConfigManager.addMsAuthAccount(MCProfile.id, MCAccessToken.access_token, MCProfile.name, MCAccessToken.expires_at, accessToken.access_token, accessToken.refresh_token)
|
||||
ConfigManager.save()
|
||||
|
||||
return ret
|
||||
} catch(error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
@ -103,7 +103,8 @@ const DEFAULT_CONFIG = {
|
||||
selectedServer: null, // Resolved
|
||||
selectedAccount: null,
|
||||
authenticationDatabase: {},
|
||||
modConfigurations: []
|
||||
modConfigurations: [],
|
||||
microsoftAuth: {}
|
||||
}
|
||||
|
||||
let config = null
|
||||
@ -331,7 +332,28 @@ exports.updateAuthAccount = function(uuid, accessToken){
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated account to the database to be stored.
|
||||
* Update the tokens of an authenticated microsoft account.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The new Access Token.
|
||||
* @param {string} msAccessToken The new Microsoft Access Token
|
||||
* @param {string} msRefreshToken The new Microsoft Refresh Token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.updateAuthAccount = function(uuid, accessToken, msAccessToken, msRefreshToken, msExpires, mcExpires){
|
||||
config.authenticationDatabase[uuid].accessToken = accessToken
|
||||
config.authenticationDatabase[uuid].expiresAt = mcExpires
|
||||
config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken
|
||||
config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken
|
||||
config.authenticationDatabase[uuid].microsoft.expires_at = msRefreshToken
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated mojang account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
@ -346,7 +368,39 @@ exports.addAuthAccount = function(uuid, accessToken, username, displayName){
|
||||
accessToken,
|
||||
username: username.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: displayName.trim()
|
||||
displayName: displayName.trim(),
|
||||
type: 'mojang'
|
||||
}
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated microsoft account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
* @param {string} name The in game name of the authenticated account.
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
* @param {string} msAccessToken The microsoft access token
|
||||
* @param {string} msRefreshToken The microsoft refresh token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.addMsAuthAccount = function(uuid, accessToken, name, mcExpires, msAccessToken, msRefreshToken, msExpires){
|
||||
config.selectedAccount = uuid
|
||||
config.authenticationDatabase[uuid] = {
|
||||
accessToken,
|
||||
username: name.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: name.trim(),
|
||||
expiresAt: mcExpires,
|
||||
type: 'microsoft',
|
||||
microsoft: {
|
||||
access_token: msAccessToken,
|
||||
refresh_token: msRefreshToken,
|
||||
expires_at: msExpires
|
||||
}
|
||||
}
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
225
app/assets/js/microsoft.js
Normal file
225
app/assets/js/microsoft.js
Normal file
@ -0,0 +1,225 @@
|
||||
// Requirements
|
||||
const request = require('request')
|
||||
|
||||
// Constants
|
||||
const clientId = '71a6e661-ee73-4166-a21a-26ce6e15b3de'
|
||||
|
||||
const tokenUri = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
|
||||
const authXBLUri = 'https://user.auth.xboxlive.com/user/authenticate'
|
||||
const authXSTSUri = 'https://xsts.auth.xboxlive.com/xsts/authorize'
|
||||
const authMCUri = 'https://api.minecraftservices.com/authentication/login_with_xbox'
|
||||
const profileURI = 'https://api.minecraftservices.com/minecraft/profile'
|
||||
|
||||
// Functions
|
||||
function requestPromise(uri, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(uri, options, (error, response, body) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else if (response.statusCode !== 200) {
|
||||
reject([response.statusCode, response.statusMessage, response])
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXBLToken(accessToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
AuthMethod: 'RPS',
|
||||
SiteName: 'user.auth.xboxlive.com',
|
||||
RpsTicket: `d=${accessToken}`
|
||||
},
|
||||
RelyingParty: 'http://auth.xboxlive.com',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXBLUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
data.token = body.Token
|
||||
data.uhs = body.DisplayClaims.xui[0].uhs
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXSTSToken(XBLToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
SandboxId: 'RETAIL',
|
||||
UserTokens: [XBLToken]
|
||||
},
|
||||
RelyingParty: 'rp://api.minecraftservices.com/',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXSTSUri, options).then(response => {
|
||||
if (response.body.XErr) {
|
||||
switch (response.body.XErr) {
|
||||
case 2148916233:
|
||||
reject({
|
||||
message: 'Your Microsoft account is not connected to an Xbox account. Please create one.<br>'
|
||||
})
|
||||
return
|
||||
|
||||
case 2148916238:
|
||||
reject({
|
||||
message: 'Since you are not yet 18 years old, an adult must add you to a family in order for you to use Helios Launcher!'
|
||||
})
|
||||
return
|
||||
|
||||
}
|
||||
reject(response.body)
|
||||
}
|
||||
resolve(response.body.Token)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getMCAccessToken(UHS, XSTSToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
const expiresAt = new Date()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
identityToken: `XBL3.0 x=${UHS};${XSTSToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(authMCUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.access_token = body.access_token
|
||||
data.expires_at = expiresAt
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Exports
|
||||
exports.getAccessToken = authCode => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
code: authCode,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
data.refresh_token = body.refresh_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.refreshAccessToken = refreshToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
refresh_token: refreshToken,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'refresh_token'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.authMinecraft = async accessToken => {
|
||||
try {
|
||||
const XBLToken = await getXBLToken(accessToken)
|
||||
const XSTSToken = await getXSTSToken(XBLToken.token)
|
||||
const MCToken = await getMCAccessToken(XBLToken.uhs, XSTSToken)
|
||||
|
||||
return MCToken
|
||||
} catch (error) {
|
||||
Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkMCStore = async function(access_token){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get({
|
||||
url: 'https://api.minecraftservices.com/entitlements/mcstore',
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + access_token
|
||||
}
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
if(body.items && body.items.length > 0) resolve(true)
|
||||
else resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.getMCProfile = MCAccessToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'get',
|
||||
headers: {
|
||||
Authorization: `Bearer ${MCAccessToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(profileURI, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
resolve(body)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
@ -17,6 +17,7 @@ const checkmarkContainer = document.getElementById('checkmarkContainer')
|
||||
const loginRememberOption = document.getElementById('loginRememberOption')
|
||||
const loginButton = document.getElementById('loginButton')
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const loginMSButton = document.getElementById('loginMSButton')
|
||||
|
||||
// Control variables.
|
||||
let lu = false, lp = false
|
||||
@ -297,4 +298,97 @@ loginButton.addEventListener('click', () => {
|
||||
loggerLogin.log('Error while logging in.', err)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
loginMSButton.addEventListener('click', (event) => {
|
||||
// Show loading stuff.
|
||||
toggleOverlay(true, false, 'msOverlay')
|
||||
loginMSButton.disabled = true
|
||||
ipcRenderer.send('openMSALoginWindow', 'open')
|
||||
})
|
||||
|
||||
ipcRenderer.on('MSALoginWindowReply', (event, ...args) => {
|
||||
if (args[0] === 'error') {
|
||||
|
||||
loginMSButton.disabled = false
|
||||
loginLoading(false)
|
||||
switch (args[1]){
|
||||
case 'AlreadyOpenException': {
|
||||
setOverlayContent('ERROR', 'There is already a login window open!', 'OK')
|
||||
setOverlayHandler(() => {
|
||||
toggleOverlay(false)
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
case 'AuthNotFinished': {
|
||||
setOverlayContent('ERROR', 'You have to finish the login process to use Helios Launcher. The window will close by itself when you have successfully logged in.', 'OK')
|
||||
setOverlayHandler(() => {
|
||||
toggleOverlay(false)
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
const queryMap = args[0]
|
||||
if (queryMap.has('error')) {
|
||||
let error = queryMap.get('error')
|
||||
let errorDesc = queryMap.get('error_description')
|
||||
if(error === 'access_denied'){
|
||||
error = 'ERRPR'
|
||||
errorDesc = 'To use the Helios Launcher, you must agree to the required permissions! Otherwise you can\'t use this launcher with Microsoft accounts.<br><br>Despite agreeing to the permissions you don\'t give us the possibility to do anything with your account, because all data will always be sent back to you (the launcher) IMMEDIATELY and WITHOUT WAY.'
|
||||
}
|
||||
setOverlayContent(error, errorDesc, 'OK')
|
||||
setOverlayHandler(() => {
|
||||
loginMSButton.disabled = false
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Disable form.
|
||||
formDisabled(true)
|
||||
|
||||
const authCode = queryMap.get('code')
|
||||
AuthManager.addMSAccount(authCode).then(account => {
|
||||
updateSelectedAccount(account)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
setTimeout(() => {
|
||||
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
|
||||
// Temporary workaround
|
||||
if (loginViewOnSuccess === VIEWS.settings) {
|
||||
prepareSettings()
|
||||
}
|
||||
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
||||
loginCancelEnabled(false) // Reset this for good measure.
|
||||
loginViewCancelHandler = null // Reset this for good measure.
|
||||
loginUsername.value = ''
|
||||
loginPassword.value = ''
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
loginLoading(false)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
|
||||
formDisabled(false)
|
||||
})
|
||||
}, 1000)
|
||||
}).catch(error => {
|
||||
loginMSButton.disabled = false
|
||||
loginLoading(false)
|
||||
setOverlayContent('ERROR', error.message ? error.message : 'An error occurred while logging in with Microsoft! For more detailed information please check the log. You can open it with CTRL + SHIFT + I.', Lang.queryJS('login.tryAgain'))
|
||||
setOverlayHandler(() => {
|
||||
formDisabled(false)
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
loggerLogin.error(error)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
* @returns {boolean} Whether or not the overlay is visible.
|
||||
*/
|
||||
function isOverlayVisible(){
|
||||
function isOverlayVisible() {
|
||||
return document.getElementById('main').hasAttribute('overlay')
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ let overlayHandlerContent
|
||||
*
|
||||
* @param {KeyboardEvent} e The keydown event.
|
||||
*/
|
||||
function overlayKeyHandler (e){
|
||||
if(e.key === 'Enter' || e.key === 'Escape'){
|
||||
function overlayKeyHandler(e) {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click()
|
||||
}
|
||||
}
|
||||
@ -30,10 +30,10 @@ function overlayKeyHandler (e){
|
||||
*
|
||||
* @param {KeyboardEvent} e The keydown event.
|
||||
*/
|
||||
function overlayKeyDismissableHandler (e){
|
||||
if(e.key === 'Enter'){
|
||||
function overlayKeyDismissableHandler(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click()
|
||||
} else if(e.key === 'Escape'){
|
||||
} else if (e.key === 'Escape') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEsc')[0].click()
|
||||
}
|
||||
}
|
||||
@ -45,12 +45,12 @@ function overlayKeyDismissableHandler (e){
|
||||
* @param {string} content The overlay content which will be shown.
|
||||
* @param {boolean} dismissable Whether or not the overlay is dismissable
|
||||
*/
|
||||
function bindOverlayKeys(state, content, dismissable){
|
||||
function bindOverlayKeys(state, content, dismissable) {
|
||||
overlayHandlerContent = content
|
||||
document.removeEventListener('keydown', overlayKeyHandler)
|
||||
document.removeEventListener('keydown', overlayKeyDismissableHandler)
|
||||
if(state){
|
||||
if(dismissable){
|
||||
if (state) {
|
||||
if (dismissable) {
|
||||
document.addEventListener('keydown', overlayKeyDismissableHandler)
|
||||
} else {
|
||||
document.addEventListener('keydown', overlayKeyHandler)
|
||||
@ -65,22 +65,22 @@ function bindOverlayKeys(state, content, dismissable){
|
||||
* @param {boolean} dismissable Optional. True to show the dismiss option, otherwise false.
|
||||
* @param {string} content Optional. The content div to be shown.
|
||||
*/
|
||||
function toggleOverlay(toggleState, dismissable = false, content = 'overlayContent'){
|
||||
if(toggleState == null){
|
||||
function toggleOverlay(toggleState, dismissable = false, content = 'overlayContent') {
|
||||
if (toggleState == null) {
|
||||
toggleState = !document.getElementById('main').hasAttribute('overlay')
|
||||
}
|
||||
if(typeof dismissable === 'string'){
|
||||
if (typeof dismissable === 'string') {
|
||||
content = dismissable
|
||||
dismissable = false
|
||||
}
|
||||
bindOverlayKeys(toggleState, content, dismissable)
|
||||
if(toggleState){
|
||||
if (toggleState) {
|
||||
document.getElementById('main').setAttribute('overlay', true)
|
||||
// Make things untabbable.
|
||||
$('#main *').attr('tabindex', '-1')
|
||||
$('#' + content).parent().children().hide()
|
||||
$('#' + content).show()
|
||||
if(dismissable){
|
||||
if (dismissable) {
|
||||
$('#overlayDismiss').show()
|
||||
} else {
|
||||
$('#overlayDismiss').hide()
|
||||
@ -88,7 +88,7 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
$('#overlayContainer').fadeIn({
|
||||
duration: 250,
|
||||
start: () => {
|
||||
if(getCurrentView() === VIEWS.settings){
|
||||
if (getCurrentView() === VIEWS.settings) {
|
||||
document.getElementById('settingsContainer').style.backgroundColor = 'transparent'
|
||||
}
|
||||
}
|
||||
@ -100,14 +100,14 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
$('#overlayContainer').fadeOut({
|
||||
duration: 250,
|
||||
start: () => {
|
||||
if(getCurrentView() === VIEWS.settings){
|
||||
if (getCurrentView() === VIEWS.settings) {
|
||||
document.getElementById('settingsContainer').style.backgroundColor = 'rgba(0, 0, 0, 0.50)'
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
$('#' + content).parent().children().hide()
|
||||
$('#' + content).show()
|
||||
if(dismissable){
|
||||
if (dismissable) {
|
||||
$('#overlayDismiss').show()
|
||||
} else {
|
||||
$('#overlayDismiss').hide()
|
||||
@ -117,7 +117,7 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
}
|
||||
}
|
||||
|
||||
function toggleServerSelection(toggleState){
|
||||
function toggleServerSelection(toggleState) {
|
||||
prepareServerSelectionList()
|
||||
toggleOverlay(toggleState, true, 'serverSelectContent')
|
||||
}
|
||||
@ -130,7 +130,7 @@ function toggleServerSelection(toggleState){
|
||||
* @param {string} acknowledge Acknowledge button text.
|
||||
* @param {string} dismiss Dismiss button text.
|
||||
*/
|
||||
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){
|
||||
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss') {
|
||||
document.getElementById('overlayTitle').innerHTML = title
|
||||
document.getElementById('overlayDesc').innerHTML = description
|
||||
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
|
||||
@ -143,8 +143,8 @@ function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss')
|
||||
*
|
||||
* @param {function} handler
|
||||
*/
|
||||
function setOverlayHandler(handler){
|
||||
if(handler == null){
|
||||
function setOverlayHandler(handler) {
|
||||
if (handler == null) {
|
||||
document.getElementById('overlayAcknowledge').onclick = () => {
|
||||
toggleOverlay(false)
|
||||
}
|
||||
@ -159,8 +159,8 @@ function setOverlayHandler(handler){
|
||||
*
|
||||
* @param {function} handler
|
||||
*/
|
||||
function setDismissHandler(handler){
|
||||
if(handler == null){
|
||||
function setDismissHandler(handler) {
|
||||
if (handler == null) {
|
||||
document.getElementById('overlayDismiss').onclick = () => {
|
||||
toggleOverlay(false)
|
||||
}
|
||||
@ -173,8 +173,8 @@ function setDismissHandler(handler){
|
||||
|
||||
document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
const listings = document.getElementsByClassName('serverListing')
|
||||
for(let i=0; i<listings.length; i++){
|
||||
if(listings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < listings.length; i++) {
|
||||
if (listings[i].hasAttribute('selected')) {
|
||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
||||
updateSelectedServer(serv)
|
||||
refreshServerStatus(true)
|
||||
@ -183,7 +183,7 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
}
|
||||
}
|
||||
// None are selected? Not possible right? Meh, handle it.
|
||||
if(listings.length > 0){
|
||||
if (listings.length > 0) {
|
||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
||||
updateSelectedServer(serv)
|
||||
toggleOverlay(false)
|
||||
@ -192,8 +192,8 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
|
||||
document.getElementById('accountSelectConfirm').addEventListener('click', () => {
|
||||
const listings = document.getElementsByClassName('accountListing')
|
||||
for(let i=0; i<listings.length; i++){
|
||||
if(listings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < listings.length; i++) {
|
||||
if (listings[i].hasAttribute('selected')) {
|
||||
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
|
||||
ConfigManager.save()
|
||||
updateSelectedAccount(authAcc)
|
||||
@ -203,7 +203,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
|
||||
}
|
||||
}
|
||||
// None are selected? Not possible right? Meh, handle it.
|
||||
if(listings.length > 0){
|
||||
if (listings.length > 0) {
|
||||
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
|
||||
ConfigManager.save()
|
||||
updateSelectedAccount(authAcc)
|
||||
@ -223,16 +223,16 @@ document.getElementById('accountSelectCancel').addEventListener('click', () => {
|
||||
})
|
||||
})
|
||||
|
||||
function setServerListingHandlers(){
|
||||
function setServerListingHandlers() {
|
||||
const listings = Array.from(document.getElementsByClassName('serverListing'))
|
||||
listings.map((val) => {
|
||||
val.onclick = e => {
|
||||
if(val.hasAttribute('selected')){
|
||||
if (val.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const cListings = document.getElementsByClassName('serverListing')
|
||||
for(let i=0; i<cListings.length; i++){
|
||||
if(cListings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < cListings.length; i++) {
|
||||
if (cListings[i].hasAttribute('selected')) {
|
||||
cListings[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
@ -242,16 +242,16 @@ function setServerListingHandlers(){
|
||||
})
|
||||
}
|
||||
|
||||
function setAccountListingHandlers(){
|
||||
function setAccountListingHandlers() {
|
||||
const listings = Array.from(document.getElementsByClassName('accountListing'))
|
||||
listings.map((val) => {
|
||||
val.onclick = e => {
|
||||
if(val.hasAttribute('selected')){
|
||||
if (val.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const cListings = document.getElementsByClassName('accountListing')
|
||||
for(let i=0; i<cListings.length; i++){
|
||||
if(cListings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < cListings.length; i++) {
|
||||
if (cListings[i].hasAttribute('selected')) {
|
||||
cListings[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
@ -261,12 +261,12 @@ function setAccountListingHandlers(){
|
||||
})
|
||||
}
|
||||
|
||||
function populateServerListings(){
|
||||
function populateServerListings() {
|
||||
const distro = DistroManager.getDistribution()
|
||||
const giaSel = ConfigManager.getSelectedServer()
|
||||
const servers = distro.getServers()
|
||||
let htmlString = ''
|
||||
for(const serv of servers){
|
||||
for (const serv of servers) {
|
||||
htmlString += `<button class="serverListing" servid="${serv.getID()}" ${serv.getID() === giaSel ? 'selected' : ''}>
|
||||
<img class="serverListingImg" src="${serv.getIcon()}"/>
|
||||
<div class="serverListingDetails">
|
||||
@ -293,12 +293,12 @@ function populateServerListings(){
|
||||
|
||||
}
|
||||
|
||||
function populateAccountListings(){
|
||||
function populateAccountListings() {
|
||||
const accountsObj = ConfigManager.getAuthAccounts()
|
||||
const accounts = Array.from(Object.keys(accountsObj), v=>accountsObj[v])
|
||||
const accounts = Array.from(Object.keys(accountsObj), v => accountsObj[v])
|
||||
let htmlString = ''
|
||||
for(let i=0; i<accounts.length; i++){
|
||||
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i===0 ? 'selected' : ''}>
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i === 0 ? 'selected' : ''}>
|
||||
<img src="https://crafatar.com/renders/head/${accounts[i].uuid}?scale=2&default=MHF_Steve&overlay">
|
||||
<div class="accountListingName">${accounts[i].displayName}</div>
|
||||
</button>`
|
||||
@ -307,12 +307,12 @@ function populateAccountListings(){
|
||||
|
||||
}
|
||||
|
||||
function prepareServerSelectionList(){
|
||||
function prepareServerSelectionList() {
|
||||
populateServerListings()
|
||||
setServerListingHandlers()
|
||||
}
|
||||
|
||||
function prepareAccountSelectionList(){
|
||||
function prepareAccountSelectionList() {
|
||||
populateAccountListings()
|
||||
setAccountListingHandlers()
|
||||
}
|
@ -387,9 +387,20 @@ function bindAuthAccountLogOut(){
|
||||
* @param {Element} val The log out button element.
|
||||
* @param {boolean} isLastAccount If this logout is on the last added account.
|
||||
*/
|
||||
function processLogOut(val, isLastAccount){
|
||||
const parent = val.closest('.settingsAuthAccount')
|
||||
const uuid = parent.getAttribute('uuid')
|
||||
function processLogOut(val, isLastAccount, skip = false) {
|
||||
data = {
|
||||
val,
|
||||
isLastAccount
|
||||
}
|
||||
if (!skip) {
|
||||
const parent = val.closest('.settingsAuthAccount')
|
||||
const uuid = parent.getAttribute('uuid')
|
||||
const account = ConfigManager.getAuthAccount(uuid)
|
||||
if (account.type === 'microsoft') {
|
||||
toggleOverlay(true, false, 'msOverlay')
|
||||
ipcRenderer.send('openMSALogoutWindow', 'open')
|
||||
}
|
||||
}
|
||||
const prevSelAcc = ConfigManager.getSelectedAccount()
|
||||
AuthManager.removeAccount(uuid).then(() => {
|
||||
if(!isLastAccount && uuid === prevSelAcc.uuid){
|
||||
@ -404,6 +415,12 @@ function processLogOut(val, isLastAccount){
|
||||
})
|
||||
}
|
||||
|
||||
ipcRenderer.on('MSALogoutWindowReply', (event, ...args) => {
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
processLogOut(data.val, data.isLastAccount, true)
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes the status of the selected account on the auth account
|
||||
* elements.
|
||||
|
145
app/login.ejs
145
app/login.ejs
@ -1,65 +1,90 @@
|
||||
<div id="loginContainer" style="display: none;">
|
||||
<div id="loginCancelContainer" style="display: none;">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
<div id="loginContainer" style="display: none">
|
||||
<div id="loginCancelContainer" style="display: none">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.png"/>
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
||||
<input id="loginUsername" class="loginField" type="text" placeholder="EMAIL OR USERNAME"/>
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.png" />
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
||||
<input id="loginUsername" class="loginField" type="text" placeholder="EMAIL OR USERNAME" />
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
||||
<input id="loginPassword" class="loginField" type="password" placeholder="PASSWORD" />
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked />
|
||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>
|
||||
.arrowLine {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 2px;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02" />
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
||||
<input id="loginPassword" class="loginField" type="password" placeholder="PASSWORD"/>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked>
|
||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://minecraft.net/en-us/store/minecraft/">Need an Account?</a>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">Your password is sent directly to mojang and never stored.</p>
|
||||
<p class="loginDisclaimerText">Vicarious Network Launcher is not affiliated with Mojang AB.</p>
|
||||
</div>
|
||||
</form>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
or <br />
|
||||
<button id="loginMSButton">Login with<br>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 604 129" style="width: auto; height: 30px">
|
||||
<title>Microsoft Logo</title>
|
||||
<path
|
||||
d="M213.2 74.3l-3.6 10.2h-.3c-.6-2.3-1.7-5.8-3.5-10l-19.3-48.5h-18.9v77.3h12.5v-47.7c0-3 0-6.4-.1-10.6-.1-2.1-.3-3.7-.4-4.9h.3c.6 3 1.3 5.2 1.8 6.6l23.2 56.4h8.8l23-56.9c.5-1.3 1-3.9 1.5-6.1h.3c-.3 5.7-.5 10.8-.6 13.9v49h13.3v-77.2h-18.2l-19.8 48.5zM263.8 47.6h13v55.4h-13zM270.4 24.2c-2.2 0-4 .8-5.5 2.2-1.5 1.4-2.3 3.2-2.3 5.4 0 2.1.8 3.9 2.3 5.3 1.5 1.4 3.3 2.1 5.5 2.1s4.1-.8 5.5-2.1c1.5-1.4 2.3-3.2 2.3-5.3s-.8-3.9-2.3-5.4c-1.3-1.4-3.2-2.2-5.5-2.2M322.9 47.1c-2.4-.5-4.9-.8-7.3-.8-5.9 0-11.3 1.3-15.8 3.9-4.5 2.6-8.1 6.2-10.4 10.7-2.4 4.6-3.6 9.9-3.6 16 0 5.3 1.2 10 3.5 14.3 2.3 4.2 5.5 7.6 9.8 9.9 4.1 2.3 8.9 3.5 14.3 3.5 6.2 0 11.5-1.3 15.7-3.7l.1-.1v-12l-.5.4c-1.9 1.4-4.1 2.6-6.3 3.3-2.3.8-4.4 1.2-6.2 1.2-5.2 0-9.3-1.5-12.2-4.8-3-3.2-4.5-7.6-4.5-13.1 0-5.7 1.5-10.2 4.6-13.5 3.1-3.3 7.2-5 12.2-5 4.2 0 8.5 1.4 12.4 4.2l.5.4v-12.7l-.1-.1c-1.7-.7-3.6-1.5-6.2-2M365.8 46.7c-3.2 0-6.2 1-8.8 3.1-2.2 1.8-3.7 4.4-5 7.5h-.1v-9.7h-13v55.4h13v-28.3c0-4.8 1-8.8 3.2-11.7 2.2-3 5-4.5 8.4-4.5 1.2 0 2.4.3 3.9.5 1.4.4 2.4.8 3.1 1.3l.5.4v-13l-.3-.1c-.9-.6-2.7-.9-4.9-.9M401.2 46.4c-9.1 0-16.4 2.7-21.5 8-5.2 5.3-7.7 12.6-7.7 21.8 0 8.6 2.6 15.6 7.6 20.7 5 5 11.8 7.6 20.3 7.6 8.9 0 16-2.7 21.1-8.1 5.2-5.4 7.7-12.6 7.7-21.5 0-8.8-2.4-15.8-7.3-20.9-4.7-5.1-11.6-7.6-20.2-7.6m10.4 42.6c-2.4 3.1-6.2 4.6-10.9 4.6s-8.5-1.5-11.2-4.8c-2.7-3.1-4-7.6-4-13.3 0-5.9 1.4-10.4 4-13.6 2.7-3.2 6.4-4.8 11.1-4.8 4.6 0 8.2 1.5 10.8 4.6 2.6 3.1 4 7.6 4 13.5-.2 6-1.3 10.7-3.8 13.8M457.7 70.6c-4.1-1.7-6.7-3-7.9-4.1-1-1-1.5-2.4-1.5-4.2 0-1.5.6-3 2.1-4s3.2-1.5 5.7-1.5c2.2 0 4.5.4 6.7 1s4.2 1.5 5.8 2.7l.5.4v-12.2l-.3-.1c-1.5-.6-3.5-1.2-5.9-1.7-2.4-.4-4.6-.6-6.4-.6-6.2 0-11.3 1.5-15.3 4.8-4 3.1-5.9 7.3-5.9 12.2 0 2.6.4 4.9 1.3 6.8.9 1.9 2.2 3.7 4 5.2 1.8 1.4 4.4 3 8 4.5 3 1.3 5.3 2.3 6.7 3.1 1.4.8 2.3 1.7 3 2.4.5.8.8 1.8.8 3.1 0 3.7-2.8 5.5-8.5 5.5-2.2 0-4.5-.4-7.2-1.3s-5.2-2.2-7.3-3.7l-.5-.4v12.7l.3.1c1.9.9 4.2 1.5 7 2.2 2.8.5 5.3.9 7.5.9 6.7 0 12.2-1.5 16.1-4.8 4-3.2 6.1-7.3 6.1-12.6 0-3.7-1-7-3.2-9.5-2.9-2.4-6.5-4.9-11.7-6.9M506.9 46.4c-9.1 0-16.4 2.7-21.5 8s-7.7 12.6-7.7 21.8c0 8.6 2.6 15.6 7.6 20.7 5 5 11.8 7.6 20.3 7.6 8.9 0 16-2.7 21.1-8.1 5.2-5.4 7.7-12.6 7.7-21.5 0-8.8-2.4-15.8-7.3-20.9-4.7-5.1-11.6-7.6-20.2-7.6m10.3 42.6c-2.4 3.1-6.2 4.6-10.9 4.6-4.8 0-8.5-1.5-11.2-4.8-2.7-3.1-4-7.6-4-13.3 0-5.9 1.4-10.4 4-13.6 2.7-3.2 6.4-4.8 11.1-4.8 4.5 0 8.2 1.5 10.8 4.6 2.6 3.1 4 7.6 4 13.5 0 6-1.3 10.7-3.8 13.8M603.9 58.3v-10.7h-13.1v-16.4l-.4.1-12.4 3.7-.3.1v12.5h-19.6v-7c0-3.2.8-5.7 2.2-7.3s3.5-2.4 6.1-2.4c1.8 0 3.7.4 5.8 1.3l.5.3v-11.3l-.3-.1c-1.8-.6-4.2-1-7.3-1-3.9 0-7.3.9-10.4 2.4-3.1 1.7-5.4 4-7.1 7.1-1.7 3-2.6 6.4-2.6 10.3v7.7h-9.1v10.6h9.1v44.8h13.1v-44.7h19.6v28.5c0 11.7 5.5 17.6 16.5 17.6 1.8 0 3.7-.3 5.5-.6 1.9-.4 3.3-.9 4.1-1.3l.1-.1v-10.7l-.5.4c-.8.5-1.5.9-2.7 1.2-1 .3-1.9.4-2.6.4-2.6 0-4.4-.6-5.7-2.1-1.2-1.4-1.8-3.7-1.8-7.1v-26.2h13.3z"
|
||||
fill="#737373" />
|
||||
<path fill="#F25022" d="M0 0h61.3v61.3h-61.3z" />
|
||||
<path fill="#7FBA00" d="M67.7 0h61.3v61.3h-61.3z" />
|
||||
<path fill="#00A4EF" d="M0 67.7h61.3v61.3h-61.3z" />
|
||||
<path fill="#FFB900" d="M67.7 67.7h61.3v61.3h-61.3z" />
|
||||
</svg></button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-edition">Need an Account?</a>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">
|
||||
Your password is sent directly to mojang and never stored.
|
||||
</p>
|
||||
<p class="loginDisclaimerText">
|
||||
Helios Launcher is not affiliated with Mojang AB.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
||||
</div>
|
@ -28,6 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="overlayContent">
|
||||
<div class="circle-loader" style="display: none;"></div>
|
||||
<span id="overlayTitle">Lorem Ipsum:<br>Finis Illud</span>
|
||||
<span id="overlayDesc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud..</span>
|
||||
<div id="overlayActionContainer">
|
||||
@ -37,5 +38,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="msOverlay" style="display: none;">
|
||||
<span><div class="circle-loader-big"></div></span><br>
|
||||
<span style="font-size: 2em;">Waiting on Microsoft...</span>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/overlay.js"></script>
|
||||
</div>
|
74
index.js
74
index.js
@ -10,6 +10,9 @@ const path = require('path')
|
||||
const semver = require('semver')
|
||||
const { pathToFileURL } = require('url')
|
||||
|
||||
const redirectUriPrefix = 'https://login.microsoftonline.com/common/oauth2/nativeclient?'
|
||||
const clientID = '71a6e661-ee73-4166-a21a-26ce6e15b3de'
|
||||
|
||||
// Setup auto updater.
|
||||
function initAutoUpdater(event, data) {
|
||||
|
||||
@ -87,6 +90,77 @@ ipcMain.on('distributionIndexDone', (event, res) => {
|
||||
// https://electronjs.org/docs/tutorial/offscreen-rendering
|
||||
app.disableHardwareAcceleration()
|
||||
|
||||
let MSALoginWindow = null
|
||||
|
||||
// Open the Microsoft Account Login window
|
||||
ipcMain.on('openMSALoginWindow', (ipcEvent, args) => {
|
||||
if (MSALoginWindow != null) {
|
||||
ipcEvent.reply('MSALoginWindowReply', 'error', 'AlreadyOpenException')
|
||||
return
|
||||
}
|
||||
MSALoginWindow = new BrowserWindow({
|
||||
title: 'Microsoft Login',
|
||||
backgroundColor: '#222222',
|
||||
width: 520,
|
||||
height: 600,
|
||||
frame: true,
|
||||
icon: getPlatformIcon('SealCircle')
|
||||
})
|
||||
|
||||
MSALoginWindow.on('closed', () => {
|
||||
|
||||
MSALoginWindow = null
|
||||
})
|
||||
|
||||
MSALoginWindow.on('close', event => {
|
||||
ipcEvent.reply('MSALoginWindowReply', 'error', 'AuthNotFinished')
|
||||
|
||||
})
|
||||
|
||||
MSALoginWindow.webContents.on('did-navigate', (event, uri, responseCode, statusText) => {
|
||||
if (uri.startsWith(redirectUriPrefix)) {
|
||||
let querys = uri.substring(redirectUriPrefix.length).split('#', 1).toString().split('&')
|
||||
let queryMap = new Map()
|
||||
|
||||
querys.forEach(query => {
|
||||
let arr = query.split('=')
|
||||
queryMap.set(arr[0], decodeURI(arr[1]))
|
||||
})
|
||||
|
||||
ipcEvent.reply('MSALoginWindowReply', queryMap)
|
||||
|
||||
MSALoginWindow.close()
|
||||
MSALoginWindow = null
|
||||
}
|
||||
})
|
||||
|
||||
MSALoginWindow.removeMenu()
|
||||
MSALoginWindow.loadURL('https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=select_account&client_id=' + clientID + '&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient')
|
||||
})
|
||||
|
||||
let MSALogoutWindow = null
|
||||
|
||||
ipcMain.on('openMSALogoutWindow', (ipcEvent, args) => {
|
||||
if (MSALogoutWindow == null) {
|
||||
MSALogoutWindow = new BrowserWindow({
|
||||
title: 'Microsoft Logout',
|
||||
backgroundColor: '#222222',
|
||||
width: 520,
|
||||
height: 600,
|
||||
frame: true,
|
||||
icon: getPlatformIcon('SealCircle')
|
||||
})
|
||||
MSALogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout')
|
||||
MSALogoutWindow.webContents.on('did-navigate', (e) => {
|
||||
setTimeout(() => {
|
||||
ipcEvent.reply('MSALogoutWindowReply')
|
||||
}, 5000)
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
app.allowRendererProcessReuse = true
|
||||
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vicariousnetwork",
|
||||
"version": "1.8.0",
|
||||
"name": "vicariousnetworklauncher",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
Loading…
Reference in New Issue
Block a user