diff --git a/src/pyelectroluxocp/apiClient.py b/src/pyelectroluxocp/apiClient.py index 507079d..e8d521a 100644 --- a/src/pyelectroluxocp/apiClient.py +++ b/src/pyelectroluxocp/apiClient.py @@ -1,6 +1,7 @@ from base64 import b64decode from datetime import datetime, timedelta -from json import loads +from json import dumps, loads +import logging from types import TracebackType from typing import Any, Dict, Optional, Type from aiohttp import ClientSession @@ -55,6 +56,9 @@ def decodeJwt(token: str): return payload +_LOGGER: logging.Logger = logging.getLogger(__package__).getChild("OneAppApiClient") + + class OneAppApiClient: def __init__(self, client_session: Optional[ClientSession] = None) -> None: self._client_session = client_session @@ -77,6 +81,7 @@ def _api_headers_base(self, token: Optional[str]): async def login_client_credentials(self, base_url: str): """Login using client credentials of the mobile application, used for fetching identity providers urls""" + _LOGGER.debug("login_client_credentials(), base_url: %s", base_url) req_params = token_url( base_url, self._api_headers_base(None), @@ -85,12 +90,24 @@ async def login_client_credentials(self, base_url: str): ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "login_client_credentials(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "login_client_credentials(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - token: ClientCredTokenResponse = await response.json() + token: ClientCredTokenResponse = response_json return ClientToken(token) async def exchange_login_user(self, base_url: str, id_token: str): """Exchange external id token to api token""" + _LOGGER.debug("exchange_login_user(), base_url: %s", base_url) decodedToken = decodeJwt(id_token) req_params = token_url( base_url, @@ -103,11 +120,23 @@ async def exchange_login_user(self, base_url: str, id_token: str): ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "exchange_login_user(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "exchange_login_user(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - token: UserTokenResponse = await response.json() + token: UserTokenResponse = response_json return UserToken(token) async def refresh_token_user(self, base_url: str, refresh_token: str): + _LOGGER.debug("refresh_token_user(), base_url: %s", base_url) req_params = token_url( base_url, self._api_headers_base(None), @@ -116,13 +145,27 @@ async def refresh_token_user(self, base_url: str, refresh_token: str): ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "refresh_token_user(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "refresh_token_user(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - newToken: UserTokenResponse = await response.json() + newToken: UserTokenResponse = response_json return UserToken(newToken) async def get_identity_providers( self, base_url: str, client_cred_token: str, username: str ): + _LOGGER.debug( + "get_identity_providers(), base_url: %s, username: %s", base_url, username + ) req_params = identity_providers_url( base_url, { @@ -134,33 +177,78 @@ async def get_identity_providers( ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_identity_providers(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_identity_providers(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: list[AuthResponse] = await response.json() + data: list[AuthResponse] = response_json return data async def get_user_metadata(self, base_url: str, token: str): + _LOGGER.debug("get_user_metadata(), base_url: %s", base_url) req_params = current_user_metadata_url(base_url, self._api_headers_base(token)) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_user_metadata(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_user_metadata(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: UserMetadataResponse = await response.json() + data: UserMetadataResponse = response_json return data async def get_appliances_list( self, base_url: str, token: str, include_metadata: bool ): + _LOGGER.debug( + "get_appliances_list(), base_url: %s, include_metadata: %s", + base_url, + include_metadata, + ) req_params = list_appliances_url( base_url, self._api_headers_base(token), include_metadata ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_appliances_list(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_appliances_list(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: list[ApplienceStatusResponse] = await response.json() + data: list[ApplienceStatusResponse] = response_json return data async def get_appliance_status( self, base_url: str, token: str, id: str, include_metadata: bool ): + _LOGGER.debug( + "get_appliance_status(), base_url: %s, id: %s, include_metadata: %s", + base_url, + id, + include_metadata, + ) req_params = get_appliance_by_id_url( base_url, self._api_headers_base(token), @@ -169,21 +257,49 @@ async def get_appliance_status( ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_appliance_status(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_appliance_status(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: ApplienceStatusResponse = await response.json() + data: ApplienceStatusResponse = response_json return data async def get_appliance_capabilities(self, base_url: str, token: str, id: str): + _LOGGER.debug( + "get_appliance_capabilities(), base_url: %s, id: %s", base_url, id + ) req_params = get_appliance_capabilities_url( base_url, self._api_headers_base(token), id ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_appliance_capabilities(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_appliance_capabilities(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: Dict[str, Any] = await response.json() + data: Dict[str, Any] = response_json return data async def get_appliances_info(self, base_url: str, token: str, ids: list[str]): + _LOGGER.debug( + "get_appliances_info(), base_url: %s, ids: %s", base_url, dumps(ids) + ) req_params = get_appliances_info_by_ids_url( base_url, self._api_headers_base(token), @@ -191,13 +307,30 @@ async def get_appliances_info(self, base_url: str, token: str, ids: list[str]): ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "get_appliances_info(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json() + _LOGGER.debug( + "get_appliances_info(), response, json: %s", dumps(response_json) + ) response.raise_for_status() - data: list[ApplianceInfoResponse] = await response.json() + data: list[ApplianceInfoResponse] = response_json return data async def execute_appliance_command( self, base_url: str, token: str, id: str, command_data: Dict[str, Any] ): + _LOGGER.debug( + "execute_appliance_command(), base_url: %s, id: %s, command_data: %s", + base_url, + id, + dumps(command_data), + ) req_params = appliance_command_url( base_url, self._api_headers_base(token), @@ -206,6 +339,13 @@ async def execute_appliance_command( ) async with await self._get_session().request(**req_params.__dict__) as response: + _LOGGER.debug( + "execute_appliance_command(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) response.raise_for_status() await response.wait_for_close() return diff --git a/src/pyelectroluxocp/gigyaClient.py b/src/pyelectroluxocp/gigyaClient.py index 6a6761c..6a5f1ec 100644 --- a/src/pyelectroluxocp/gigyaClient.py +++ b/src/pyelectroluxocp/gigyaClient.py @@ -1,3 +1,4 @@ +import logging import random import time from types import TracebackType @@ -111,6 +112,9 @@ def getOAuth1Signature( return calcSignature(base_string, secret_key) +_LOGGER: logging.Logger = logging.getLogger(__package__).getChild("GigyaClient") + + class GigyaClient: def __init__( self, domain: str, api_key: str, client_session: Optional[ClientSession] = None @@ -131,6 +135,7 @@ def _generate_nonce(self): async def get_ids(self): # https://socialize.eu1.gigya.com/socialize.getIDs + _LOGGER.debug("get_ids()") url = f"https://socialize.{self._domain}/socialize.getIDs" async with await self._get_session().get( url, @@ -143,12 +148,27 @@ async def get_ids(self): "targetEnv": "mobile", }, ) as response: + _LOGGER.debug( + "get_ids(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json(content_type=None) + _LOGGER.debug( + "get_ids(), response, json: %s", + jsonstringify(response_json), + ) response.raise_for_status() - data: SocializeGetIdsResponse = await response.json(content_type=None) + data: SocializeGetIdsResponse = response_json return data async def login_session(self, username: str, password: str, gmid: str, ucid: str): # https://accounts.eu1.gigya.com/accounts.login + _LOGGER.debug( + "login_session(), username: %s, gmid: %s, ucid: %s", username, gmid, ucid + ) url = f"https://accounts.{self._domain}/accounts.login" async with await self._get_session().post( url, @@ -165,14 +185,27 @@ async def login_session(self, username: str, password: str, gmid: str, ucid: str "ucid": ucid, }, ) as response: + _LOGGER.debug( + "login_session(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json(content_type=None) + _LOGGER.debug( + "login_session(), response, json: %s", + jsonstringify(response_json), + ) response.raise_for_status() - data: LoginResponse = await response.json(content_type=None) + data: LoginResponse = response_json return data async def get_JWT( self, session_token: str, session_secret: str, gmid: str, ucid: str ): # https://accounts.eu1.gigya.com/accounts.getJWT + _LOGGER.debug("get_JWT(), gmid: %s, ucid: %s", gmid, ucid) url = f"https://accounts.{self._domain}/accounts.getJWT" data_params = { @@ -193,8 +226,20 @@ async def get_JWT( ) async with await self._get_session().post(url, data=data_params) as response: + _LOGGER.debug( + "get_JWT(), response, requestUlr: %s, requestHeaders: %s, responseStatus: %i, responseHeaders: %s", + response.request_info.url, + response.request_info.headers, + response.status, + response.headers, + ) + response_json = await response.json(content_type=None) + _LOGGER.debug( + "get_JWT(), response, json: %s", + jsonstringify(response_json), + ) response.raise_for_status() - data: GetJWTResponse = await response.json(content_type=None) + data: GetJWTResponse = response_json return data async def login_user(self, username: str, password: str): diff --git a/src/pyelectroluxocp/oneAppApi.py b/src/pyelectroluxocp/oneAppApi.py index a2cb2a2..1599e3b 100644 --- a/src/pyelectroluxocp/oneAppApi.py +++ b/src/pyelectroluxocp/oneAppApi.py @@ -9,6 +9,13 @@ from .webSocketClient import WebSocketClient from .gigyaClient import GigyaClient from .apiModels import AuthResponse, WebSocketResponse +from importlib.metadata import version +import logging + +_LOGGER: logging.Logger = logging.getLogger(__package__).getChild("OneAppApi") + +_LOGGER.debug("aiohttp version, %s", version("aiohttp")) +_LOGGER.debug("asyncio version, %s", version("asyncio")) class OneAppApi: @@ -32,7 +39,6 @@ def __init__( self._client_session = client_session self._close_session = False self._api_client = OneAppApiClient(client_session) - pass async def get_client_cred_token(self): """Login using client credentials of the mobile application, used for fetching identity providers urls""" @@ -40,8 +46,10 @@ async def get_client_cred_token(self): self._client_cred_token is not None and not self._client_cred_token.should_renew() ): + _LOGGER.debug("get_client_cred_token(), still valid token") return self._client_cred_token + _LOGGER.debug("get_client_cred_token(), need to refresh token") base_url = await self._get_base_url() token = await self._api_client.login_client_credentials(base_url) self._client_cred_token = token @@ -58,7 +66,7 @@ async def connect_websocket(self, appliance_ids: list[str]): "version": "2", } ws_client = await self._get_websocket_client() - + _LOGGER.debug("connect_websocket(), headers: %s", dumps(headers)) # Connect to websocket and don't wait task = asyncio.create_task(ws_client.connect(headers)) self._running_tasks.add(task) @@ -86,6 +94,10 @@ async def get_user_token(self): """ if self._user_token is not None: if not self._user_token.should_renew(): + _LOGGER.debug( + "get_user_token(), return existing token, expiresAt: %s", + self._user_token.expiresAt, + ) return self._user_token base_url = await self._get_base_url() @@ -107,6 +119,7 @@ async def get_user_token(self): async def get_user_metadata(self): """Get details about user and preferences""" + _LOGGER.debug("get_user_metadata()") token = await self._get_formatted_user_token() base_url = await self._get_base_url() @@ -115,6 +128,7 @@ async def get_user_metadata(self): async def get_appliances_list(self, include_metadata: bool = False): """Get list of all user's appliances""" + _LOGGER.debug("get_appliances_list(), include_metadata: %s", include_metadata) token = await self._get_formatted_user_token() base_url = await self._get_base_url() @@ -125,6 +139,11 @@ async def get_appliances_list(self, include_metadata: bool = False): async def get_appliance_status(self, id: str, include_metadata: bool = False): """Get current status of appliance by id""" + _LOGGER.debug( + "get_appliance_capabilities(), id: %s, include_metadata: %s", + id, + include_metadata, + ) token = await self._get_formatted_user_token() base_url = await self._get_base_url() @@ -135,6 +154,7 @@ async def get_appliance_status(self, id: str, include_metadata: bool = False): async def get_appliance_capabilities(self, id: str): """Get appliance capabilities""" + _LOGGER.debug("get_appliance_capabilities(), id: %s", id) token = await self._get_formatted_user_token() base_url = await self._get_base_url() @@ -143,6 +163,7 @@ async def get_appliance_capabilities(self, id: str): async def get_appliances_info(self, ids: list[str]): """Get multiple appliances info""" + _LOGGER.debug("get_appliances_info(), ids: %s", dumps(ids)) token = await self._get_formatted_user_token() baseUrl = await self._get_base_url() @@ -151,6 +172,11 @@ async def get_appliances_info(self, ids: list[str]): async def execute_appliance_command(self, id: str, command_data: Dict[str, Any]): """Execute command for appliance""" + _LOGGER.debug( + "execute_appliance_command(), id: %s, command_data: %s", + id, + dumps(command_data), + ) token = await self._get_formatted_user_token() base_url = await self._get_base_url() result = await self._api_client.execute_appliance_command( @@ -166,6 +192,10 @@ async def watch_for_appliance_state_updates( """Fetch current appliance state and watch for state changes""" def handle_websocket_response(responseData: WebSocketResponse): + _LOGGER.debug( + "watch_for_appliance_state_updates().handle_websocket_response, responseData: %s", + dumps(responseData), + ) for appliance_update_data in responseData.get("Payload").get("Appliances"): appliance_id = appliance_update_data.get("ApplianceId") if appliance_id in appliance_ids: @@ -179,6 +209,10 @@ def handle_websocket_response(responseData: WebSocketResponse): callback(appliance_state_update_dict) def handle_disconnected_or_connected_event(): + _LOGGER.debug( + "watch_for_appliance_state_updates().handle_disconnected_or_connected_event" + ) + async def async_impl(): appliances_states = await self.get_appliances_list(False) for applianceState in appliances_states: @@ -243,10 +277,23 @@ async def _get_identity_providers(self): async def _get_base_url(self) -> str: if self._client_cred_token is None: + _LOGGER.debug( + "_get_base_url(), client_cred is not set, return BASE_URL: %s", + BASE_URL, + ) return BASE_URL if self._identity_providers is None: + _LOGGER.debug( + "_get_base_url(), _identity_providers is not set, return BASE_URL: %s", + BASE_URL, + ) return BASE_URL + providers = await self._get_identity_providers() + _LOGGER.debug( + "_get_base_url(), getting identity providers, return first result.httpRegionalBaseUrl: %s", + providers[0]["httpRegionalBaseUrl"], + ) return providers[0]["httpRegionalBaseUrl"] async def _get_regional_websocket_base_url(self):