Skip to content

Commit f3367c5

Browse files
committed
fix(api): use web login for e-Connect to register the client for long-polling
1 parent 276192b commit f3367c5

File tree

6 files changed

+539
-5
lines changed

6 files changed

+539
-5
lines changed

src/elmo/api/client.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99

1010
from .. import query as q
1111
from ..__about__ import __version__
12-
from ..utils import _camel_to_snake_case, _sanitize_session_id
12+
from ..systems import ELMO_E_CONNECT
13+
from ..utils import (
14+
_camel_to_snake_case,
15+
_sanitize_session_id,
16+
extract_session_id_from_html,
17+
)
1318
from .decorators import require_lock, require_session
1419
from .exceptions import (
1520
CodeError,
@@ -49,14 +54,15 @@ def __init__(self, base_url=None, domain=None, session_id=None):
4954
self._session_id = session_id
5055
self._panel = None
5156
self._lock = Lock()
57+
5258
# Debug
5359
_LOGGER.debug(f"Client | Library version: {__version__}")
5460
_LOGGER.debug(f"Client | Router: {self._router._base_url}")
5561
_LOGGER.debug(f"Client | Domain: {self._domain}")
5662

5763
def auth(self, username, password):
5864
"""Authenticate the client and retrieves the access token. This method uses
59-
the Authentication API.
65+
the Authentication API, or the web login form if the base_url is Elmo E-Connect.
6066
6167
Args:
6268
username: the Username used for the authentication.
@@ -69,11 +75,28 @@ def auth(self, username, password):
6975
the `ElmoClient` instance.
7076
"""
7177
try:
78+
if self._router._base_url == ELMO_E_CONNECT:
79+
# Web login is required for Elmo E-Connect because, at the moment, the
80+
# e-Connect Cloud API login does not register the client session in the backend.
81+
# This prevents the client from attaching to server events (e.g. long polling updates).
82+
web_login_url = f"https://webservice.elmospa.com/{self._domain}"
83+
payload = {
84+
"IsDisableAccountCreation": "True",
85+
"IsAllowThemeChange": "True",
86+
"UserName": username,
87+
"Password": password,
88+
"RememberMe": "false",
89+
}
90+
_LOGGER.debug("Client | e-Connect Web Login detected")
91+
web_response = self._session.post(web_login_url, data=payload)
92+
web_response.raise_for_status()
93+
94+
# API login
7295
payload = {"username": username, "password": password}
7396
if self._domain is not None:
7497
payload["domain"] = self._domain
7598

76-
_LOGGER.debug("Client | Client Authentication")
99+
_LOGGER.debug("Client | API Authentication")
77100
response = self._session.get(self._router.auth, params=payload)
78101
response.raise_for_status()
79102
except HTTPError as err:
@@ -84,8 +107,11 @@ def auth(self, username, password):
84107

85108
# Store the session_id and the panel details (if available)
86109
data = response.json()
87-
self._session_id = data["SessionId"]
88110
self._panel = {_camel_to_snake_case(k): v for k, v in data.get("Panel", {}).items()}
111+
if self._router._base_url == ELMO_E_CONNECT:
112+
self._session_id = extract_session_id_from_html(web_response.text)
113+
else:
114+
self._session_id = data["SessionId"]
89115

90116
# Register the redirect URL and try the authentication again
91117
if data["Redirect"]:

src/elmo/utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import re
33
from functools import lru_cache
44

5+
from .api.exceptions import ParseError
6+
57
_LOGGER = logging.getLogger(__name__)
68

79

@@ -58,3 +60,29 @@ def _camel_to_snake_case(name):
5860
name = name.lower()
5961

6062
return name
63+
64+
65+
def extract_session_id_from_html(html_content: str) -> str:
66+
"""Extract the session ID from the HTML source containing the specific JavaScript block.
67+
68+
This function uses a raw string (r"") for the regex pattern to avoid escaping issues.
69+
The regex pattern is designed to find "var sessionId = '...'" and capture the ID within the quotes.
70+
It captures any character except the closing single quote.
71+
72+
Args:
73+
html_content (str): The HTML source code as a string.
74+
75+
Returns:
76+
str: The extracted session ID string.
77+
78+
Raises:
79+
ParseError: If the session ID is not found in the HTML content.
80+
"""
81+
pattern = r"var\s+sessionId\s*=\s*'([^']+)'"
82+
match = re.search(pattern, html_content)
83+
if match:
84+
return match.group(1)
85+
else:
86+
_LOGGER.error("Client | Session ID not found in e-Connect status page.")
87+
_LOGGER.debug("Client | HTML content: %s", html_content)
88+
raise ParseError("Session ID not found in e-Connect status page.")

0 commit comments

Comments
 (0)