Hard-reload on integrity expiring during request;
Implement a way to hard-reload the application when the integrity token expires during a request, or inside its retry loop
This commit is contained in:
1 parent
f65c0922c2
commit
7d14b51221
4 files changed
+50
-9
No files matched your search
+14
-1
@@ -19,7 +19,20 @@ class ExitRequest(MinerException):
|
|||||||
if args:
|
if args:
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
else:
|
else:
|
||||||
super().__init__("Unknown error during request")
|
super().__init__("Application was requested to exit")
|
||||||
|
|
||||||
|
|
||||||
|
class ReloadRequest(MinerException):
|
||||||
|
"""
|
||||||
|
Raised when the application is requested to reload by an expired request.
|
||||||
|
|
||||||
|
Intended for internal use only.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args: object):
|
||||||
|
if args:
|
||||||
|
super().__init__(*args)
|
||||||
|
else:
|
||||||
|
super().__init__("Application was requested to reload its state")
|
||||||
|
|
||||||
|
|
||||||
class RequestException(MinerException):
|
class RequestException(MinerException):
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ except Exception:
|
|||||||
finally:
|
finally:
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||||
|
client.print(_("gui", "status", "exiting"))
|
||||||
loop.run_until_complete(client.shutdown())
|
loop.run_until_complete(client.shutdown())
|
||||||
if not client.gui.close_requested:
|
if not client.gui.close_requested:
|
||||||
client.print(_("status", "terminated"))
|
client.print(_("status", "terminated"))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from gui import GUIManager
|
|||||||
from channel import Channel
|
from channel import Channel
|
||||||
from websocket import WebsocketPool
|
from websocket import WebsocketPool
|
||||||
from inventory import DropsCampaign
|
from inventory import DropsCampaign
|
||||||
from exceptions import MinerException, LoginException, CaptchaRequired, ExitRequest
|
from exceptions import MinerException, LoginException, CaptchaRequired, ExitRequest, ReloadRequest
|
||||||
from utils import (
|
from utils import (
|
||||||
CHARS_HEX_LOWER,
|
CHARS_HEX_LOWER,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -74,6 +74,20 @@ class _AuthState:
|
|||||||
def integrity_expired(self) -> bool:
|
def integrity_expired(self) -> bool:
|
||||||
return datetime.now(timezone.utc) >= self.integrity_expires
|
return datetime.now(timezone.utc) >= self.integrity_expires
|
||||||
|
|
||||||
|
def _delattr(self, attr: str) -> None:
|
||||||
|
if hasattr(self, attr):
|
||||||
|
delattr(self, attr)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
self._delattr("user_id")
|
||||||
|
self._delattr("device_id")
|
||||||
|
self._delattr("session_id")
|
||||||
|
self._delattr("access_token")
|
||||||
|
self._delattr("client_version")
|
||||||
|
self._delattr("integrity_token")
|
||||||
|
self._delattr("integrity_expires")
|
||||||
|
self._logged_in.clear()
|
||||||
|
|
||||||
async def _login(self) -> str:
|
async def _login(self) -> str:
|
||||||
logger.debug("Login flow started")
|
logger.debug("Login flow started")
|
||||||
login_form: LoginForm = self._twitch.gui.login
|
login_form: LoginForm = self._twitch.gui.login
|
||||||
@@ -302,7 +316,6 @@ class Twitch:
|
|||||||
|
|
||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
start_time = time()
|
start_time = time()
|
||||||
self.gui.print(_("gui", "status", "exiting"))
|
|
||||||
self.stop_watching()
|
self.stop_watching()
|
||||||
if self._watching_task is not None:
|
if self._watching_task is not None:
|
||||||
self._watching_task.cancel()
|
self._watching_task.cancel()
|
||||||
@@ -310,13 +323,19 @@ class Twitch:
|
|||||||
if self._mnt_task is not None:
|
if self._mnt_task is not None:
|
||||||
self._mnt_task.cancel()
|
self._mnt_task.cancel()
|
||||||
self._mnt_task = None
|
self._mnt_task = None
|
||||||
# close session, save cookies and stop websocket
|
# stop websocket, close session and save cookies
|
||||||
|
await self.websocket.stop(clear_topics=True)
|
||||||
if self._session is not None:
|
if self._session is not None:
|
||||||
cookie_jar = cast(aiohttp.CookieJar, self._session.cookie_jar)
|
cookie_jar = cast(aiohttp.CookieJar, self._session.cookie_jar)
|
||||||
cookie_jar.save(COOKIES_PATH)
|
cookie_jar.save(COOKIES_PATH)
|
||||||
await self._session.close()
|
await self._session.close()
|
||||||
self._session = None
|
self._session = None
|
||||||
await self.websocket.stop()
|
self._drop_update = None
|
||||||
|
self.games.clear()
|
||||||
|
self._drops.clear()
|
||||||
|
self.channels.clear()
|
||||||
|
self.inventory.clear()
|
||||||
|
self._auth_state.clear()
|
||||||
# wait at least half a second + whatever it takes to complete the closing
|
# wait at least half a second + whatever it takes to complete the closing
|
||||||
# this allows aiohttp to safely close the session
|
# this allows aiohttp to safely close the session
|
||||||
await asyncio.sleep(start_time + 0.5 - time())
|
await asyncio.sleep(start_time + 0.5 - time())
|
||||||
@@ -378,12 +397,17 @@ class Twitch:
|
|||||||
return self.games[game]
|
return self.games[game]
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
while True:
|
||||||
try:
|
try:
|
||||||
await self._run()
|
await self._run()
|
||||||
|
except ReloadRequest:
|
||||||
|
await self.shutdown()
|
||||||
|
continue
|
||||||
except ExitRequest:
|
except ExitRequest:
|
||||||
pass
|
pass
|
||||||
except aiohttp.ContentTypeError as exc:
|
except aiohttp.ContentTypeError as exc:
|
||||||
raise MinerException(_("login", "unexpected_content")) from exc
|
raise MinerException(_("login", "unexpected_content")) from exc
|
||||||
|
break
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
"""
|
"""
|
||||||
@@ -943,7 +967,7 @@ class Twitch:
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def request(
|
async def request(
|
||||||
self, method: str, url: str, **kwargs
|
self, method: str, url: str, *, invalidate_after: datetime | None = None, **kwargs
|
||||||
) -> abc.AsyncIterator[aiohttp.ClientResponse]:
|
) -> abc.AsyncIterator[aiohttp.ClientResponse]:
|
||||||
session = await self.get_session()
|
session = await self.get_session()
|
||||||
method = method.upper()
|
method = method.upper()
|
||||||
@@ -953,6 +977,8 @@ class Twitch:
|
|||||||
for delay in ExponentialBackoff(maximum=3*60):
|
for delay in ExponentialBackoff(maximum=3*60):
|
||||||
if self.gui.close_requested:
|
if self.gui.close_requested:
|
||||||
raise ExitRequest()
|
raise ExitRequest()
|
||||||
|
elif invalidate_after is not None and datetime.now(timezone.utc) >= invalidate_after:
|
||||||
|
raise ReloadRequest()
|
||||||
try:
|
try:
|
||||||
response: aiohttp.ClientResponse | None = None
|
response: aiohttp.ClientResponse | None = None
|
||||||
done, pending = await asyncio.wait(
|
done, pending = await asyncio.wait(
|
||||||
@@ -990,7 +1016,8 @@ class Twitch:
|
|||||||
"POST",
|
"POST",
|
||||||
"https://gql.twitch.tv/gql",
|
"https://gql.twitch.tv/gql",
|
||||||
json=op,
|
json=op,
|
||||||
headers=auth_state.gql_headers(integrity=True)
|
headers=auth_state.gql_headers(integrity=True),
|
||||||
|
invalidate_after=auth_state.integrity_expires,
|
||||||
) as response:
|
) as response:
|
||||||
response_json: JsonType = await response.json()
|
response_json: JsonType = await response.json()
|
||||||
gql_logger.debug(f"GQL Response: {response_json}")
|
gql_logger.debug(f"GQL Response: {response_json}")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from typing import (
|
|||||||
import yarl
|
import yarl
|
||||||
|
|
||||||
from constants import JsonType
|
from constants import JsonType
|
||||||
from exceptions import ExitRequest
|
from exceptions import ExitRequest, ReloadRequest
|
||||||
from constants import _resource_path as resource_path # noqa
|
from constants import _resource_path as resource_path # noqa
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -77,7 +77,7 @@ def task_wrapper(
|
|||||||
async def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
async def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
||||||
try:
|
try:
|
||||||
await afunc(*args, **kwargs)
|
await afunc(*args, **kwargs)
|
||||||
except ExitRequest:
|
except (ExitRequest, ReloadRequest):
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Exception in task")
|
logger.exception("Exception in task")
|
||||||
|
|||||||
Reference in new issue
Block a user