From 96ff42f05a726c0d150cb8a4d0732915074fad34 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 7 Oct 2021 05:10:14 +0200 Subject: [PATCH] [cli/utils] Skip logout when using Qt/GTK, faster logout on Windows --- legendary/cli.py | 6 +-- legendary/utils/webview_login.py | 74 ++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/legendary/cli.py b/legendary/cli.py index eea5394..d0d47d0 100644 --- a/legendary/cli.py +++ b/legendary/cli.py @@ -163,11 +163,7 @@ class LegendaryCLI: sid = sid.strip('"') exchange_token = self.core.auth_sid(sid) else: - def callback(web_sid): - exchange_code = self.core.auth_sid(web_sid) - return exchange_code and self.core.auth_code(exchange_code) - - if do_webview_login(callback=callback): + if do_webview_login(callback_sid=self.core.auth_sid, callback_code=self.core.auth_code): logger.info(f'Successfully logged in as "{self.core.lgd.userdata["displayName"]}" via WebView') else: logger.error('WebView login attempt failed, please see log for details.') diff --git a/legendary/utils/webview_login.py b/legendary/utils/webview_login.py index f961319..c28dd90 100644 --- a/legendary/utils/webview_login.py +++ b/legendary/utils/webview_login.py @@ -2,6 +2,8 @@ import logging import json import webbrowser +from legendary import __version__ + logger = logging.getLogger('WebViewHelper') webview_available = True @@ -17,7 +19,8 @@ except Exception as e: login_url = 'https://www.epicgames.com/id/login' sid_url = 'https://www.epicgames.com/id/api/redirect?' -logout_url = 'https://www.epicgames.com/id/logout?productName=epic-games&redirectUrl=https://www.epicgames.com/site/' +logout_url = 'https://www.epicgames.com/id/logout?productName=epic-games&redirectUrl=' + login_url +goodbye_url = 'https://legendary.gl/goodbye' window_js = ''' window.ue = { signinprompt: { @@ -47,11 +50,10 @@ sid_req.send(); class MockLauncher: - def __init__(self, callback=None): - self.callback = callback + def __init__(self, callback_sid, callback_code): + self.callback_sid = callback_sid + self.callback_code = callback_code self.window = None - self.exchange_code = None - self.sid = None self.inject_js = True self.destroy_on_load = False self.callback_result = None @@ -59,17 +61,19 @@ class MockLauncher: def on_loaded(self): url = self.window.get_current_url() logger.debug(f'Loaded url: {url.partition("?")[0]}') - # make sure JS necessary window. stuff is available + + if self.destroy_on_load: + logger.info('Closing login window...') + self.window.destroy() + return + + # Inject JS so required window.ue stuff is available if self.inject_js: self.window.evaluate_js(window_js) if 'logout' in url: # prepare to close browser after logout redirect self.destroy_on_load = True - elif self.destroy_on_load: - # close browser after logout - logger.info('Closing web view...') - self.window.destroy() def nop(self, *args, **kwargs): return @@ -78,37 +82,51 @@ class MockLauncher: webbrowser.open(url) def set_exchange_code(self, exchange_code): + self.inject_js = False logger.debug('Got exchange code (stage 1)!') - # we cannot use this exchange code as our login would be invalidated - # after logging out on the website. Hence we do the dance of using - # the SID to create *another* exchange code which will create a session - # that remains valid after logging out. - self.exchange_code = exchange_code + # The default Windows webview retains cookies, GTK/Qt do not. Therefore we can + # skip logging out on those platforms and directly use the exchange code we're given. + # On windows we have to do a little dance with the SID to create a session that + # remains valid after logging out in the embedded browser. + if self.window.gui.renderer in ('gtkwebkit2', 'qtwebengine', 'qtwebkit'): + self.destroy_on_load = True + try: + self.callback_result = self.callback_code(exchange_code) + except Exception as e: + logger.error(f'Logging in via exchange-code failed with {e!r}') + finally: + # We cannot destroy the browser from here, + # so we'll load a small goodbye site first. + self.window.load_url(goodbye_url) def trigger_sid_exchange(self, *args, **kwargs): - self.inject_js = False - # first obtain SID, then log out - self.window.evaluate_js(get_sid_js) + # check if code-based login hasn't already set the destroy flag + if not self.destroy_on_load: + logger.debug(f'Injecting SID JS') + # inject JS to get SID API response and call our API + self.window.evaluate_js(get_sid_js) def login_sid(self, sid_json): + # Try SID login, then log out try: j = json.loads(sid_json) - self.sid = j['sid'] - logger.debug(f'Got SID (stage 2)!') - if self.callback: - logger.debug(f'Calling login callback...') - self.callback_result = self.callback(self.sid) + sid = j['sid'] + logger.debug(f'Got SID (stage 2)! Executing sid login callback...') + exchange_code = self.callback_sid(sid) + if exchange_code: + self.callback_result = self.callback_code(exchange_code) except Exception as e: - logger.error(f'Loading SID response failed with {e!r}') + logger.error(f'SID login failed with {e!r}') finally: logger.debug('Starting browser logout...') self.window.load_url(logout_url) -def do_webview_login(callback=None): - api = MockLauncher(callback=callback) - logger.info('Opening web view with Epic Games Login...') - window = webview.create_window('Epic Login', url=login_url, width=1024, height=1024, js_api=api) +def do_webview_login(callback_sid=None, callback_code=None): + api = MockLauncher(callback_sid=callback_sid, callback_code=callback_code) + logger.info('Opening Epic Games login window...') + window = webview.create_window(f'Legendary {__version__} - Epic Games Account Login', + url=login_url, width=768, height=1024, js_api=api) api.window = window window.loaded += api.on_loaded webview.start()