Skip to content

Commit

Permalink
Fix xCloud error on startup when using the application in a non xClou…
Browse files Browse the repository at this point in the history
…d supported country #1193
  • Loading branch information
unknownskl committed Apr 11, 2024
1 parent 0acf96c commit 3a41b74
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 85 deletions.
72 changes: 26 additions & 46 deletions main/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,67 +153,47 @@ export default class Application {
_xCloudApi:xCloudApi
_xboxWorker:xboxWorker

authenticationCompleted(){
authenticationCompleted(streamingTokens, webToken){
this.log('electron', __filename+'[authenticationCompleted()] authenticationCompleted called')
// const tokens = this._authentication._tokens
this._xHomeApi = new xCloudApi(this, streamingTokens.xHomeToken.getDefaultRegion().baseUri.substring(8), streamingTokens.xHomeToken.data.gsToken, 'home')

this._authentication._xal.getWebToken(this._authentication._tokenStore).then((webToken) => {
this.log('electron', __filename+'[authenticationCompleted()] getWebToken resolved:', webToken.data.Token, webToken.data.DisplayClaims.xui[0].uhs)
if(streamingTokens.xCloudToken !== null){
this._xCloudApi = new xCloudApi(this, streamingTokens.xCloudToken.getDefaultRegion().baseUri.substring(8), streamingTokens.xCloudToken.data.gsToken, 'cloud')
}

this._webApi = new xboxWebApi({
userToken: webToken.data.Token,
uhs: webToken.data.DisplayClaims.xui[0].uhs,
})
this._webApi = new xboxWebApi({
userToken: webToken.data.Token,
uhs: webToken.data.DisplayClaims.xui[0].uhs,
})

console.log('xboxapi', this._webApi)
this._authentication._isAuthenticating = false
this._authentication._isAuthenticated = true

this._webApi.getProvider('profile').get('/users/me/profile/settings?settings=GameDisplayName,GameDisplayPicRaw,Gamerscore,Gamertag').then((result) => {
if(result.profileUsers.length > 0) {
for(const setting in result.profileUsers[0].settings){
this._webApi.getProvider('profile').get('/users/me/profile/settings?settings=GameDisplayName,GameDisplayPicRaw,Gamerscore,Gamertag').then((result) => {
if(result.profileUsers.length > 0) {
for(const setting in result.profileUsers[0].settings){

if(result.profileUsers[0].settings[setting].id === 'Gamertag'){
this._store.set('user.gamertag', result.profileUsers[0].settings[setting].value)
if(result.profileUsers[0].settings[setting].id === 'Gamertag'){
this._store.set('user.gamertag', result.profileUsers[0].settings[setting].value)

} else if(result.profileUsers[0].settings[setting].id === 'GameDisplayPicRaw'){
this._store.set('user.gamerpic', result.profileUsers[0].settings[setting].value)
} else if(result.profileUsers[0].settings[setting].id === 'GameDisplayPicRaw'){
this._store.set('user.gamerpic', result.profileUsers[0].settings[setting].value)

} else if(result.profileUsers[0].settings[setting].id === 'Gamerscore'){
this._store.set('user.gamerscore', result.profileUsers[0].settings[setting].value)
}
} else if(result.profileUsers[0].settings[setting].id === 'Gamerscore'){
this._store.set('user.gamerscore', result.profileUsers[0].settings[setting].value)
}
}
}

// Run workers
this._xboxWorker = new xboxWorker(this)

}).catch((error) => {
this.log('electron', __filename+'[authenticationCompleted()] Failed to retrieve user profile:', error)
dialog.showMessageBox({
message: 'Error: Failed to retrieve user profile:'+ JSON.stringify(error),
type: 'error',
})
})
}).catch((error) => {
this.log('electron', __filename+'[authenticationCompleted()] Failed to retrieve web tokens:', error)
dialog.showMessageBox({
message: 'Error: Failed to retrieve web tokens:'+ JSON.stringify(error),
type: 'error',
})
})

this._authentication._xal.getStreamingToken(this._authentication._tokenStore).then((streamingTokens) => {
this.log('electron', __filename+'[authenticationCompleted()] Using hosts for xCloud and xHome:')
this.log('electron', __filename+'[authenticationCompleted()] - xHome:', streamingTokens.xHomeToken.getDefaultRegion().baseUri.substring(8))
this.log('electron', __filename+'[authenticationCompleted()] - xCloud:', streamingTokens.xCloudToken.getDefaultRegion().baseUri.substring(8))
this._xHomeApi = new xCloudApi(this, streamingTokens.xHomeToken.getDefaultRegion().baseUri.substring(8), streamingTokens.xHomeToken.data.gsToken, 'home')
this._xCloudApi = new xCloudApi(this, streamingTokens.xCloudToken.getDefaultRegion().baseUri.substring(8), streamingTokens.xCloudToken.data.gsToken, 'cloud')

// Let IPC know we are ready
// Run workers
this._xboxWorker = new xboxWorker(this)
this._ipc.onUserLoaded()

}).catch((error) => {
this.log('electron', __filename+'[authenticationCompleted()] Failed to retrieve streaming tokens:', error)
this.log('electron', __filename+'[authenticationCompleted()] Failed to retrieve user profile:', error)
dialog.showMessageBox({
message: 'Error: Failed to retrieve streaming tokens:'+ JSON.stringify(error),
message: 'Error: Failed to retrieve user profile:'+ JSON.stringify(error),
type: 'error',
})
})
Expand Down
95 changes: 73 additions & 22 deletions main/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { session, dialog } from 'electron'
import { createWindow } from './helpers'
import Application from './application'
import { Xal } from 'xal-node'
import { Xal, TokenStore } from 'xal-node'
import AuthTokenStore from './helpers/tokenstore'


Expand All @@ -26,72 +26,97 @@ export default class Authentication {
}

checkAuthentication(){
this._application.log('authenticationV2', __filename+'[checkAuthentication()] Starting token check...')
this._application.log('authenticationV2', '[checkAuthentication()] Starting token check...')
if(this._tokenStore.hasValidAuthTokens()){
this._application.log('authenticationV2', __filename+'[checkAuthentication()] Tokens are valid.')
this._application.log('authenticationV2', '[checkAuthentication()] Tokens are valid.')
this.startSilentFlow()

return true

} else {
if(this._tokenStore.getUserToken() !== undefined){
// We have a user token, lets try to refresh it.
this._application.log('authenticationV2', __filename+'[checkAuthentication()] Tokens are expired but we have a user token. Lets try to refresh the tokens.')
this._application.log('authenticationV2', '[checkAuthentication()] Tokens are expired but we have a user token. Lets try to refresh the tokens.')
this.startSilentFlow()

return true

} else {
this._application.log('authenticationV2', __filename+'[checkAuthentication()] No tokens are present.')
this._application.log('authenticationV2', '[checkAuthentication()] No tokens are present.')
return false
}
}
}

startSilentFlow(){
this._application.log('authenticationV2', __filename+'[startSilentFlow()] Starting silent flow...')
this._application.log('authenticationV2', '[startSilentFlow()] Starting silent flow...')
this._isAuthenticating = true

this._xal.refreshTokens(this._tokenStore).then((result) => {
this._application.log('authenticationV2', __filename+'[startSilentFlow()] Refreshed tokens:', result)
this._xal.refreshTokens(this._tokenStore).then(() => {
this._application.log('authenticationV2', '[startSilentFlow()] Tokens have been refreshed')

this._application.authenticationCompleted()
this._isAuthenticating = false
this._isAuthenticated = true
this._appLevel = 2
this.getStreamingToken(this._tokenStore).then((streamingTokens) => {
if(streamingTokens.xCloudToken !== null){
this._application.log('authenticationV2', '[startSilentFlow()] Retrieved both xHome and xCloud tokens')
this._appLevel = 2
} else {
this._application.log('authenticationV2', '[startSilentFlow()] Retrieved xHome token only')
this._appLevel = 1
}

this._xal.getWebToken(this._tokenStore).then((webToken) => {
this._application.log('authenticationV2', __filename+'[startSilentFlow()] Web token received')

this._application.authenticationCompleted(streamingTokens, webToken)

}).catch((error) => {
this._application.log('authenticationV2', __filename+'[startSilentFlow()] Failed to retrieve web tokens:', error)
dialog.showMessageBox({
message: 'Error: Failed to retrieve web tokens:'+ JSON.stringify(error),
type: 'error',
})
})

}).catch((err) => {
this._application.log('authenticationV2', '[startSilentFlow()] Failed to retrieve streaming tokens:', err)
dialog.showMessageBox({
message: 'Error: Failed to retrieve streaming tokens:'+ JSON.stringify(err),
type: 'error',
})
})

}).catch((err) => {
this._application.log('authenticationV2', __filename+'[startSilentFlow()] Error refreshing tokens:', err)
this._application.log('authenticationV2', '[startSilentFlow()] Error refreshing tokens:', err)
this._tokenStore.clear()
})
}

startAuthflow(){
this._application.log('authenticationV2', __filename+'[startAuthflow()] Starting authentication flow')
this._application.log('authenticationV2', '[startAuthflow()] Starting authentication flow')

this._xal.getRedirectUri().then((redirect) => {
this.openAuthWindow(redirect.sisuAuth.MsaOauthRedirect)

this._authCallback = (redirectUri) => {
this._application.log('authenticationV2', __filename+'[startAuthFlow()] Got redirect URI:', redirectUri)
this._application.log('authenticationV2', '[startAuthFlow()] Got redirect URI:', redirectUri)
this._xal.authenticateUser(this._tokenStore, redirect, redirectUri).then((result) => {
this._application.log('authenticationV2', __filename+'[startAuthFlow()] Authenticated user:', result)
this._application.log('authenticationV2', '[startAuthFlow()] Authenticated user:', result)

this.startSilentFlow()

}).catch((err) => {
this._application.log('authenticationV2', __filename+'[startAuthFlow()] Error authenticating user:', err)
this._application.log('authenticationV2', '[startAuthFlow()] Error authenticating user:', err)
dialog.showErrorBox('Error', 'Error authenticating user. Error details: '+JSON.stringify(err))
})
}
}).catch((err) => {
this._application.log('authenticationV2', __filename+'[startAuthFlow()] Error getting redirect URI:', err)
this._application.log('authenticationV2', '[startAuthFlow()] Error getting redirect URI:', err)
dialog.showErrorBox('Error', 'Error getting redirect URI. Error details: '+JSON.stringify(err))
})
}

startWebviewHooks(){
this._application.log('authenticationV2', __filename+'[startWebviewHooks()] Starting webview hooks')
this._application.log('authenticationV2', '[startWebviewHooks()] Starting webview hooks')

session.defaultSession.webRequest.onHeadersReceived({
urls: [
Expand All @@ -101,13 +126,13 @@ export default class Authentication {
}, (details, callback) => {

if(details.responseHeaders.Location !== undefined && details.responseHeaders.Location[0].includes(this._xal._app.RedirectUri)){
this._application.log('authenticationV2', __filename+'[startWebviewHooks()] Got redirect URI from OAUTH:', details.responseHeaders.Location[0])
this._application.log('authenticationV2', '[startWebviewHooks()] Got redirect URI from OAUTH:', details.responseHeaders.Location[0])
this._authWindow.close()

if(this._authCallback !== undefined){
this._authCallback(details.responseHeaders.Location[0])
} else {
this._application.log('authenticationV2', __filename+'[startWebviewHooks()] Authentication Callback is not defined:', this._authCallback)
this._application.log('authenticationV2', '[startWebviewHooks()] Authentication Callback is not defined:', this._authCallback)
dialog.showErrorBox('Error', 'Authentication Callback is not defined. Error details: '+JSON.stringify(this._authCallback))
}

Expand All @@ -129,10 +154,36 @@ export default class Authentication {
this._authWindow = authWindow

this._authWindow.on('close', () => {
this._application.log('authenticationV2', __filename+'[openAuthWindow()] Closed auth window')
this._application.log('authenticationV2', '[openAuthWindow()] Closed auth window')
// @TODO: What to do?
})
}

async getStreamingToken(tokenStore:TokenStore){
const sisuToken = tokenStore.getSisuToken()
if(sisuToken === undefined)
throw new Error('Sisu token is missing. Please authenticate first')

const xstsToken = await this._xal.doXstsAuthorization(sisuToken, 'http://gssv.xboxlive.com/')

if(this._xal._xhomeToken === undefined || this._xal._xhomeToken.getSecondsValid() <= 60){
this._xal._xhomeToken = await this._xal.getStreamToken(xstsToken, 'xhome')
}

if(this._xal._xcloudToken === undefined || this._xal._xcloudToken.getSecondsValid() <= 60){
try {
this._xal._xcloudToken = await this._xal.getStreamToken(xstsToken, 'xgpuweb')
} catch(error){
try {
this._xal._xcloudToken = await this._xal.getStreamToken(xstsToken, 'xgpuwebf2p')
} catch(error){
this._xal._xcloudToken = null
}
}
}

return { xHomeToken: this._xal._xhomeToken, xCloudToken: this._xal._xcloudToken }
}


}
4 changes: 4 additions & 0 deletions main/ipc/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,16 @@ export default class IpcApp extends IpcBase {
})

// Tokenstore values
const xCloudTokenValid = (this._application._authentication._xal._xcloudToken !== null) ? this._application._authentication._xal._xcloudToken.getSecondsValid() : 'None'
returnValue.push({
name: 'XAL',
data: [
{ name: 'User token expires in', value: this._application._authentication._tokenStore.getUserToken().getSecondsValid() },
{ name: 'Sisu token expires in', value: this._application._authentication._tokenStore.getSisuToken().getSecondsValid() },
{ name: 'Authenticated user', value: this._application._authentication._tokenStore.getSisuToken().getGamertag() + ' ('+this._application._authentication._tokenStore.getSisuToken().getUserHash()+')' },
{ name: '', value: '' },
{ name: 'xHome Token validity', value: this._application._authentication._xal._xhomeToken.getSecondsValid() },
{ name: 'xCloud Token validity', value: xCloudTokenValid },

],
})
Expand Down
2 changes: 1 addition & 1 deletion main/ipc/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default class IpcBase {
}

send(channel, args:EventArgs){
this._application.log('Ipc:Send', 'Sending event: ['+channel+']', args)
this._application.log('Ipc:Send', 'Sending event: ['+channel+']', JSON.stringify(args))
this._application._mainWindow.webContents.send(channel, {
action: args.action,
id: args.id,
Expand Down
32 changes: 18 additions & 14 deletions main/ipc/xcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,29 @@ export default class IpcxCloud extends IpcBase {
}

onUserLoaded(){
this._application._xCloudApi.getTitles().then((titles:any) => {
this._titleManager.setCloudTitles(titles).then(() => {
if(this._application._xCloudApi !== undefined){
this._application._xCloudApi.getTitles().then((titles:any) => {
this._titleManager.setCloudTitles(titles).then(() => {

this._application.log('Ipc:xCloud', 'Titlemanager has loaded all titles.')
this._titlesAreLoaded = true
this._application.log('Ipc:xCloud', 'Titlemanager has loaded all titles.')
this._titlesAreLoaded = true

// Uncomment to delay the process of loading data
// setTimeout(() => {
// this._titlesAreLoaded = true
// }, 5000)
// Uncomment to delay the process of loading data
// setTimeout(() => {
// this._titlesAreLoaded = true
// }, 5000)

}).catch((error) => {
this._application.log('Ipc:xCloud', 'Titlemanager is unable to load titles:', error)
console.log('Error setting xCloud titles:', error)
})

}).catch((error) => {
this._application.log('Ipc:xCloud', 'Titlemanager is unable to load titles:', error)
console.log('Error setting xCloud titles:', error)
this._application.log('Ipc:xCloud', 'Could not load recent titles:', error)
})

}).catch((error) => {
this._application.log('Ipc:xCloud', 'Could not load recent titles:', error)
})
} else {
this._application.log('Ipc:xCloud', 'xCloud IPC is not preloading titles as we dont have a valid token')
}
}

// Returns the last played titles (stream titles)
Expand Down
2 changes: 1 addition & 1 deletion main/webui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class WebUI {
const rawSettings = this._application._store.get('settings', defaultSettings) as object
const settings = {...defaultSettings, ...rawSettings}

this._application.log('webui', 'Settings:', settings, rawSettings)
// this._application.log('webui', 'Settings:', settings, rawSettings)

if(settings.webui_autostart === true){
this.startServer(settings.webui_port)
Expand Down
Loading

0 comments on commit 3a41b74

Please sign in to comment.