From 7e03594b82b849445ce54c0251bd6c3f6a4571dd Mon Sep 17 00:00:00 2001 From: Matt Dennis Date: Tue, 20 Oct 2026 14:57:29 +0100 Subject: [PATCH] fix: parse single-quoted SSO config values on the Hive login page Since 2026-10-20 https://sso.hivehome.com/ emits its bootstrap config as window.HiveSSOPoolId = 'eu-west-1_...' (single quotes, spaces around =) instead of window.HiveSSOPoolId="...". The string-replace JSON builder in getLoginInfo() only works with the old format, so it raises JSONDecodeError, returns None, and HiveAuthAsync.async_init crashes with AttributeError on self.data.get. Home Assistant (pinned to 1.0.9) then fails to set up the Hive integration after every restart (home-assistant/core#187516). Parse the window.* assignments with a regex that accepts either quote character. Backport of the dev-branch fix for a 1.0.10 patch release. Co-Authored-By: Claude Fable 5.1 --- src/api/hive_api.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/api/hive_api.py b/src/api/hive_api.py index 6f21c817..df91229a 100644 --- a/src/api/hive_api.py +++ b/src/api/hive_api.py @@ -3,6 +3,7 @@ # pylint: skip-file import json import logging +import re import requests import urllib3 @@ -12,6 +13,11 @@ _LOGGER = logging.getLogger(__name__) +# The SSO bootstrap page embeds its config as `window. = ` +# assignments. The value quoting changed from double to single quotes on +# 2026-10-20, so accept either (and require the closing quote to match). +_SSO_ASSIGNMENT = re.compile(r"""window\.(\w+)\s*=\s*(["'])(.*?)\2""") + class HiveApi: """Hive API Code.""" @@ -106,7 +112,7 @@ def refreshTokens(self, tokens={}): jsc = ( "{" + ",".join( - ('"' + str(i) + '": ' '"' + str(t) + '" ' for i, t in tokens.items()) + ('"' + str(i) + '": "' + str(t) + '" ' for i, t in tokens.items()) ) + "}" ) @@ -140,14 +146,12 @@ def getLoginInfo(self): "getLoginInfo - Login info response status: %s", data.status_code ) html = PyQuery(data.content) - json_data = json.loads( - '{"' - + (html("script:first").text()) - .replace(",", ', "') - .replace("=", '":') - .replace("window.", "") - + "}" - ) + json_data = { + key: value + for key, _quote, value in _SSO_ASSIGNMENT.findall( + html("script:first").text() + ) + } loginData = {} loginData.update({"UPID": json_data["HiveSSOPoolId"]}) @@ -311,7 +315,7 @@ def setState(self, n_type, n_id, **kwargs): jsc = ( "{" + ",".join( - ('"' + str(i) + '": ' '"' + str(t) + '" ' for i, t in kwargs.items()) + ('"' + str(i) + '": "' + str(t) + '" ' for i, t in kwargs.items()) ) + "}" )