python-homely 0.1.8__tar.gz → 0.1.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_homely-0.1.8/src/python_homely.egg-info → python_homely-0.1.9}/PKG-INFO +1 -1
- {python_homely-0.1.8 → python_homely-0.1.9}/pyproject.toml +1 -1
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/websocket.py +9 -3
- {python_homely-0.1.8 → python_homely-0.1.9/src/python_homely.egg-info}/PKG-INFO +1 -1
- {python_homely-0.1.8 → python_homely-0.1.9}/tests/test_sdk.py +41 -1
- {python_homely-0.1.8 → python_homely-0.1.9}/LICENSE +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/README.md +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/setup.cfg +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/__init__.py +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/client.py +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/exceptions.py +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/models.py +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/homely/py.typed +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/python_homely.egg-info/SOURCES.txt +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/python_homely.egg-info/dependency_links.txt +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/python_homely.egg-info/requires.txt +0 -0
- {python_homely-0.1.8 → python_homely-0.1.9}/src/python_homely.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-homely"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.9"
|
|
8
8
|
description = "Async Python client for the Homely cloud API, built for Home Assistant but usable anywhere."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -370,10 +370,13 @@ class HomelyWebSocket:
|
|
|
370
370
|
|
|
371
371
|
self._set_status("Connecting")
|
|
372
372
|
try:
|
|
373
|
+
# SECURITY: never enable the engineio logger. It logs the full
|
|
374
|
+
# connection URL, which carries the bearer token in the query
|
|
375
|
+
# string (?token=Bearer <access_token>), leaking it to HA logs.
|
|
373
376
|
self.socket = socketio.AsyncClient(
|
|
374
377
|
reconnection=False,
|
|
375
378
|
logger=False,
|
|
376
|
-
engineio_logger=
|
|
379
|
+
engineio_logger=False,
|
|
377
380
|
)
|
|
378
381
|
|
|
379
382
|
async def connect() -> None:
|
|
@@ -411,8 +414,11 @@ class HomelyWebSocket:
|
|
|
411
414
|
"locationId": str(self.location_id),
|
|
412
415
|
"token": bearer_token,
|
|
413
416
|
}
|
|
414
|
-
|
|
415
|
-
|
|
417
|
+
# NOTE: do NOT send a "partner" query parameter. Homely's websocket
|
|
418
|
+
# server rejects it ("Partner <code> is not supported") and closes
|
|
419
|
+
# the connection immediately, causing a connect/disconnect loop.
|
|
420
|
+
# partner_code is accepted for API compatibility but intentionally
|
|
421
|
+
# unused here.
|
|
416
422
|
query = urlencode(query_params)
|
|
417
423
|
url = f"{self.websocket_url}?{query}"
|
|
418
424
|
_LOGGER.debug("WebSocket connecting %s to %s", self._ctx(), self.websocket_url)
|
|
@@ -95,7 +95,7 @@ class _FakeAsyncCallable:
|
|
|
95
95
|
async def test_sdk_exports_public_symbols():
|
|
96
96
|
"""The SDK should expose a clean public surface."""
|
|
97
97
|
assert auth_header_value("token") == "Bearer token"
|
|
98
|
-
assert __version__ == "0.1.
|
|
98
|
+
assert __version__ == "0.1.9"
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
async def test_authenticate_returns_typed_token():
|
|
@@ -499,6 +499,46 @@ async def test_websocket_connection_state_uses_engineio_transport_health():
|
|
|
499
499
|
assert state.status_mismatch is False
|
|
500
500
|
|
|
501
501
|
|
|
502
|
+
async def test_connect_keeps_engineio_logger_disabled(monkeypatch):
|
|
503
|
+
"""SECURITY: the Engine.IO logger must stay off.
|
|
504
|
+
|
|
505
|
+
When enabled it logs the full connection URL, which carries the bearer
|
|
506
|
+
token in the query string (?token=Bearer <access_token>), leaking it to
|
|
507
|
+
the consuming application's logs.
|
|
508
|
+
"""
|
|
509
|
+
import socketio
|
|
510
|
+
|
|
511
|
+
captured: dict[str, object] = {}
|
|
512
|
+
connect_args: dict[str, object] = {}
|
|
513
|
+
|
|
514
|
+
class _FakeSocket:
|
|
515
|
+
def __init__(self, **kwargs: object) -> None:
|
|
516
|
+
captured.update(kwargs)
|
|
517
|
+
|
|
518
|
+
def on(self, *args: object, **kwargs: object) -> None:
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
async def connect(self, url: str, *args: object, **kwargs: object) -> None:
|
|
522
|
+
connect_args["url"] = url
|
|
523
|
+
|
|
524
|
+
# connect() does a local `import socketio`, so patch the real module.
|
|
525
|
+
monkeypatch.setattr(socketio, "AsyncClient", _FakeSocket)
|
|
526
|
+
|
|
527
|
+
ws = HomelyWebSocket(
|
|
528
|
+
location_id="loc-1",
|
|
529
|
+
token="super-secret-token",
|
|
530
|
+
on_data_update=lambda _data: None,
|
|
531
|
+
partner_code=1275,
|
|
532
|
+
)
|
|
533
|
+
await ws.connect()
|
|
534
|
+
|
|
535
|
+
assert captured.get("engineio_logger") is False
|
|
536
|
+
assert captured.get("logger") is False
|
|
537
|
+
# Homely rejects a "partner" query parameter and closes the connection,
|
|
538
|
+
# so it must never be sent even when partner_code is set.
|
|
539
|
+
assert "partner" not in str(connect_args.get("url"))
|
|
540
|
+
|
|
541
|
+
|
|
502
542
|
async def test_websocket_sync_token_only_reconnects_when_transport_is_down():
|
|
503
543
|
"""Token sync should not nudge healthy sockets, but should reconnect dead ones."""
|
|
504
544
|
ws = HomelyWebSocket(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|