diff --git a/custom_components/smartthinq_sensors/__init__.py b/custom_components/smartthinq_sensors/__init__.py index 1c6bfc4d..af3d37d4 100644 --- a/custom_components/smartthinq_sensors/__init__.py +++ b/custom_components/smartthinq_sensors/__init__.py @@ -13,6 +13,7 @@ ConfigEntry, ) from homeassistant.const import ( + CONF_CLIENT_ID, CONF_REGION, CONF_TOKEN, MAJOR_VERSION, @@ -145,7 +146,7 @@ async def get_oauth_info_from_login( return None async def create_client_from_token( - self, token: str, oauth_url: str | None = None + self, token: str, oauth_url: str | None = None, client_id: str | None = None ) -> ClientAsync: """Create a new client using refresh token.""" return await ClientAsync.from_token( @@ -154,6 +155,7 @@ async def create_client_from_token( language=self._language, oauth_url=oauth_url, aiohttp_session=self._client_session, + client_id=client_id, ) @@ -179,7 +181,7 @@ def _notify_message( @callback -def _migrate_old_entry_config(hass: HomeAssistant, entry: ConfigEntry) -> None: +def _migrate_old_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Migrate an old config entry if available.""" old_key = "outh_url" # old conf key with typo error if old_key not in entry.data: @@ -192,6 +194,19 @@ def _migrate_old_entry_config(hass: HomeAssistant, entry: ConfigEntry) -> None: ) +@callback +def _add_clientid_config_entry( + hass: HomeAssistant, entry: ConfigEntry, client_id: str +) -> None: + """Add the client id to the config entry, so it can be reused.""" + if CONF_CLIENT_ID in entry.data or not client_id: + return + + hass.config_entries.async_update_entry( + entry, data={**entry.data, CONF_CLIENT_ID: client_id} + ) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up SmartThinQ integration from a config entry.""" @@ -205,11 +220,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.warning(msg) return False - _migrate_old_entry_config(hass, entry) + _migrate_old_config_entry(hass, entry) region = entry.data[CONF_REGION] language = entry.data[CONF_LANGUAGE] refresh_token = entry.data[CONF_TOKEN] oauth2_url = None # entry.data.get(CONF_OAUTH2_URL) + client_id: str | None = entry.data.get(CONF_CLIENT_ID) use_api_v2 = entry.data.get(CONF_USE_API_V2, False) use_ha_session = entry.data.get(CONF_USE_HA_SESSION, False) @@ -249,7 +265,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # raising ConfigEntryNotReady platform setup will be retried lge_auth = LGEAuthentication(hass, region, language, use_ha_session) try: - client = await lge_auth.create_client_from_token(refresh_token, oauth2_url) + client = await lge_auth.create_client_from_token( + refresh_token, oauth2_url, client_id + ) except (AuthenticationError, InvalidCredentialError) as exc: if (auth_retry := hass.data[DOMAIN].get(AUTH_RETRY, 0)) >= MAX_AUTH_RETRY: hass.data.pop(DOMAIN) @@ -290,6 +308,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("ThinQ client connected") + if not client_id: + _add_clientid_config_entry(hass, entry, client.client_id) + try: lge_devices, unsupported_devices, discovered_devices = await lge_devices_setup( hass, client diff --git a/custom_components/smartthinq_sensors/config_flow.py b/custom_components/smartthinq_sensors/config_flow.py index aa185b02..3f666a0f 100644 --- a/custom_components/smartthinq_sensors/config_flow.py +++ b/custom_components/smartthinq_sensors/config_flow.py @@ -1,4 +1,5 @@ """Config flow for LG SmartThinQ.""" + from __future__ import annotations from collections.abc import Mapping @@ -12,6 +13,7 @@ from homeassistant import config_entries from homeassistant.const import ( CONF_BASE, + CONF_CLIENT_ID, CONF_PASSWORD, CONF_REGION, CONF_TOKEN, @@ -75,6 +77,7 @@ def __init__(self) -> None: self._region: str | None = None self._language: str | None = None self._token: str | None = None + self._client_id: str | None = None self._oauth2_url: str | None = None self._use_ha_session = False @@ -242,6 +245,7 @@ async def _check_connection(self, lge_auth: LGEAuthentication) -> int: if not client.has_devices: return RESULT_NO_DEV + self._client_id = client.client_id return RESULT_SUCCESS async def _manage_error(self, error_code: int, is_user_step=False) -> FlowResult: @@ -269,6 +273,8 @@ def _save_config_entry(self) -> FlowResult: CONF_TOKEN: self._token, CONF_USE_API_V2: True, } + if self._client_id: + data[CONF_CLIENT_ID] = self._client_id if self._oauth2_url: data[CONF_OAUTH2_URL] = self._oauth2_url if self._use_ha_session: diff --git a/custom_components/smartthinq_sensors/wideq/core_async.py b/custom_components/smartthinq_sensors/wideq/core_async.py index 4b684b31..b7cf8a89 100644 --- a/custom_components/smartthinq_sensors/wideq/core_async.py +++ b/custom_components/smartthinq_sensors/wideq/core_async.py @@ -165,6 +165,7 @@ def __init__( timeout: int = DEFAULT_TIMEOUT, oauth_url: str | None = None, session: aiohttp.ClientSession | None = None, + client_id: str | None = None, ): """ Create the CoreAsync object @@ -180,7 +181,7 @@ def __init__( self._language = language self._timeout = aiohttp.ClientTimeout(total=timeout) self._oauth_url = oauth_url - self._client_id = None + self._client_id = client_id self._lang_pack_url = None if session: @@ -191,12 +192,12 @@ def __init__( self._managed_session = True @property - def country(self): + def country(self) -> str: """Return the used country.""" return self._country @property - def language(self): + def language(self) -> str: """Return the used language.""" return self._language @@ -205,6 +206,11 @@ def lang_pack_url(self): """Return the used language.""" return self._lang_pack_url + @property + def client_id(self) -> str | None: + """Return the associated client_id.""" + return self._client_id + async def close(self): """Close the managed session on exit.""" if self._managed_session and self._session: @@ -220,6 +226,7 @@ def _get_session(self) -> aiohttp.ClientSession: def _get_client_id(self, user_number: str | None = None) -> str: """Generate a new clent ID or return existing.""" if self._client_id is not None: + _LOGGER.info("Client ID: %s", self._client_id) return self._client_id if user_number is None: return None @@ -1425,6 +1432,13 @@ def auth(self) -> Auth: assert False, "unauthenticated" return self._auth + @property + def client_id(self) -> str | None: + """Return the associated client_id.""" + if not self._auth: + return None + return self._auth.gateway.core.client_id + @property def session(self) -> Session: """Return the Session object associated to this client.""" @@ -1506,6 +1520,7 @@ async def from_user_login( language: str = DEFAULT_LANGUAGE, oauth_url: str | None = None, aiohttp_session: aiohttp.ClientSession | None = None, + client_id: str | None = None, enable_emulation: bool = False, ) -> ClientAsync: """ @@ -1517,7 +1532,11 @@ async def from_user_login( """ core = CoreAsync( - country, language, oauth_url=oauth_url, session=aiohttp_session + country, + language, + oauth_url=oauth_url, + session=aiohttp_session, + client_id=client_id, ) try: gateway = await Gateway.discover(core) @@ -1545,6 +1564,7 @@ async def from_token( language: str = DEFAULT_LANGUAGE, oauth_url: str | None = None, aiohttp_session: aiohttp.ClientSession | None = None, + client_id: str | None = None, enable_emulation: bool = False, ) -> ClientAsync: """ @@ -1556,7 +1576,11 @@ async def from_token( """ core = CoreAsync( - country, language, oauth_url=oauth_url, session=aiohttp_session + country, + language, + oauth_url=oauth_url, + session=aiohttp_session, + client_id=client_id, ) try: gateway = await Gateway.discover(core)