HeliosLauncher/index.js
Daniel Scalzi eae490b0a7
Microsoft Auth intial implementation.
This commit introduces support for Microsoft Authentication via the settings menu. There are a few known issues and TODOs which should not be hard to fix.
Once the auth flow from the settings screen is complete, the auth folow for first-time login (ie no accounts added yet) must be added. Should not be very difficult. The plan is to add one more view with two login buttons. The Mojang button will just bring you to the login page. The microsoft one will launch OAuth2.
2022-02-06 23:03:06 -05:00

326 lines
10 KiB
JavaScript

const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
// Requirements
const { app, BrowserWindow, ipcMain, Menu } = require('electron')
const autoUpdater = require('electron-updater').autoUpdater
const ejse = require('ejs-electron')
const fs = require('fs')
const isDev = require('./app/assets/js/isdev')
const path = require('path')
const semver = require('semver')
const { pathToFileURL } = require('url')
const { AZURE_CLIENT_ID, MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./app/assets/js/ipcconstants')
// Setup auto updater.
function initAutoUpdater(event, data) {
if(data){
autoUpdater.allowPrerelease = true
} else {
// Defaults to true if application version contains prerelease components (e.g. 0.12.1-alpha.1)
// autoUpdater.allowPrerelease = true
}
if(isDev){
autoUpdater.autoInstallOnAppQuit = false
autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
}
if(process.platform === 'darwin'){
autoUpdater.autoDownload = false
}
autoUpdater.on('update-available', (info) => {
event.sender.send('autoUpdateNotification', 'update-available', info)
})
autoUpdater.on('update-downloaded', (info) => {
event.sender.send('autoUpdateNotification', 'update-downloaded', info)
})
autoUpdater.on('update-not-available', (info) => {
event.sender.send('autoUpdateNotification', 'update-not-available', info)
})
autoUpdater.on('checking-for-update', () => {
event.sender.send('autoUpdateNotification', 'checking-for-update')
})
autoUpdater.on('error', (err) => {
event.sender.send('autoUpdateNotification', 'realerror', err)
})
}
// Open channel to listen for update actions.
ipcMain.on('autoUpdateAction', (event, arg, data) => {
switch(arg){
case 'initAutoUpdater':
console.log('Initializing auto updater.')
initAutoUpdater(event, data)
event.sender.send('autoUpdateNotification', 'ready')
break
case 'checkForUpdate':
autoUpdater.checkForUpdates()
.catch(err => {
event.sender.send('autoUpdateNotification', 'realerror', err)
})
break
case 'allowPrereleaseChange':
if(!data){
const preRelComp = semver.prerelease(app.getVersion())
if(preRelComp != null && preRelComp.length > 0){
autoUpdater.allowPrerelease = true
} else {
autoUpdater.allowPrerelease = data
}
} else {
autoUpdater.allowPrerelease = data
}
break
case 'installUpdateNow':
autoUpdater.quitAndInstall()
break
default:
console.log('Unknown argument', arg)
break
}
})
// Redirect distribution index event from preloader to renderer.
ipcMain.on('distributionIndexDone', (event, res) => {
event.sender.send('distributionIndexDone', res)
})
// Disable hardware acceleration.
// https://electronjs.org/docs/tutorial/offscreen-rendering
app.disableHardwareAcceleration()
const REDIRECT_URI_PREFIX = 'https://login.microsoftonline.com/common/oauth2/nativeclient?'
// Microsoft Auth Login
let msftAuthWindow
let msftAuthSuccess
ipcMain.on(MSFT_OPCODE.OPEN_LOGIN, (ipcEvent) => {
if (msftAuthWindow) {
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN)
return
}
msftAuthSuccess = false
msftAuthWindow = new BrowserWindow({
title: 'Microsoft Login',
backgroundColor: '#222222',
width: 520,
height: 600,
frame: true,
icon: getPlatformIcon('SealCircle')
})
msftAuthWindow.on('closed', () => {
msftAuthWindow = undefined
})
msftAuthWindow.on('close', () => {
if(!msftAuthSuccess) {
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED)
}
})
msftAuthWindow.webContents.on('did-navigate', (_, uri) => {
if (uri.startsWith(REDIRECT_URI_PREFIX)) {
let queries = uri.substring(REDIRECT_URI_PREFIX.length).split('#', 1).toString().split('&')
let queryMap = {}
queries.forEach(query => {
const [name, value] = query.split('=')
queryMap[name] = decodeURI(value)
})
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.SUCCESS, queryMap)
msftAuthSuccess = true
msftAuthWindow.close()
msftAuthWindow = null
}
})
msftAuthWindow.removeMenu()
msftAuthWindow.loadURL(`https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=select_account&client_id=${AZURE_CLIENT_ID}&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient`)
})
// Microsoft Auth Logout
let msftLogoutWindow
let msftLogoutSuccess
ipcMain.on(MSFT_OPCODE.OPEN_LOGOUT, (ipcEvent, uuid, isLastAccount) => {
if (msftLogoutWindow) {
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN)
return
}
msftLogoutSuccess = false
msftLogoutWindow = new BrowserWindow({
title: 'Microsoft Logout',
backgroundColor: '#222222',
width: 520,
height: 600,
frame: true,
icon: getPlatformIcon('SealCircle')
})
msftLogoutWindow.on('closed', () => {
msftLogoutWindow = undefined
})
msftLogoutWindow.on('close', () => {
if(!msftLogoutSuccess) {
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED)
}
})
msftLogoutWindow.webContents.on('did-navigate', () => {
setTimeout(() => {
if(msftLogoutWindow) {
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount)
msftLogoutSuccess = true
if(msftLogoutWindow) {
msftLogoutWindow.close()
msftLogoutWindow = null
}
}
}, 5000)
})
msftLogoutWindow.removeMenu()
msftLogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout')
})
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow() {
win = new BrowserWindow({
width: 980,
height: 552,
icon: getPlatformIcon('SealCircle'),
frame: false,
webPreferences: {
preload: path.join(__dirname, 'app', 'assets', 'js', 'preloader.js'),
nodeIntegration: true,
contextIsolation: false
},
backgroundColor: '#171614'
})
remoteMain.enable(win.webContents)
ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)))
win.loadURL(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString())
/*win.once('ready-to-show', () => {
win.show()
})*/
win.removeMenu()
win.resizable = true
win.on('closed', () => {
win = null
})
}
function createMenu() {
if(process.platform === 'darwin') {
// Extend default included application menu to continue support for quit keyboard shortcut
let applicationSubMenu = {
label: 'Application',
submenu: [{
label: 'About Application',
selector: 'orderFrontStandardAboutPanel:'
}, {
type: 'separator'
}, {
label: 'Quit',
accelerator: 'Command+Q',
click: () => {
app.quit()
}
}]
}
// New edit menu adds support for text-editing keyboard shortcuts
let editSubMenu = {
label: 'Edit',
submenu: [{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:'
}, {
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:'
}, {
type: 'separator'
}, {
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
}, {
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
}, {
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
}, {
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
}]
}
// Bundle submenus into a single template and build a menu object with it
let menuTemplate = [applicationSubMenu, editSubMenu]
let menuObject = Menu.buildFromTemplate(menuTemplate)
// Assign it to the application
Menu.setApplicationMenu(menuObject)
}
}
function getPlatformIcon(filename){
let ext
switch(process.platform) {
case 'win32':
ext = 'ico'
break
case 'darwin':
case 'linux':
default:
ext = 'png'
break
}
return path.join(__dirname, 'app', 'assets', 'images', `${filename}.${ext}`)
}
app.on('ready', createWindow)
app.on('ready', createMenu)
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})