From 380bba04088dedd279cfa6ebcd678436b10108f1 Mon Sep 17 00:00:00 2001 From: thekiwismarthome Date: Tue, 31 Mar 2026 19:57:01 +1300 Subject: [PATCH] fix: proxy OpenFoodFacts API through HA backend to fix browser CORS errors; implement products/delete handler --- .../shopping_list_manager/__init__.py | 6 ++ .../websocket/handlers.py | 59 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/custom_components/shopping_list_manager/__init__.py b/custom_components/shopping_list_manager/__init__.py index b8ff12b..78656ce 100644 --- a/custom_components/shopping_list_manager/__init__.py +++ b/custom_components/shopping_list_manager/__init__.py @@ -209,6 +209,12 @@ async def _async_register_websocket_handlers( handlers.websocket_get_product_substitutes, ) + # OpenFoodFacts proxy + websocket_api.async_register_command( + hass, + handlers.websocket_off_fetch, + ) + # Categories handlers websocket_api.async_register_command( hass, diff --git a/custom_components/shopping_list_manager/websocket/handlers.py b/custom_components/shopping_list_manager/websocket/handlers.py index bf9cfe4..99597de 100644 --- a/custom_components/shopping_list_manager/websocket/handlers.py +++ b/custom_components/shopping_list_manager/websocket/handlers.py @@ -40,6 +40,7 @@ from ..const import ( WS_TYPE_PRODUCTS_ADD, WS_TYPE_PRODUCTS_UPDATE, WS_TYPE_PRODUCTS_DELETE, + WS_TYPE_OFF_FETCH, WS_TYPE_CATEGORIES_GET_ALL, WS_TYPE_LOYALTY_GET_ALL, WS_TYPE_LOYALTY_ADD, @@ -1106,6 +1107,64 @@ def websocket_get_categories( ) +# ============================================================================= +# OPENFOODFACTS PROXY HANDLERS +# ============================================================================= + +@websocket_api.websocket_command( + { + vol.Required("type"): WS_TYPE_OFF_FETCH, + vol.Optional("query"): str, + vol.Optional("barcode"): str, + vol.Optional("page_size", default=5): int, + } +) +@websocket_api.async_response +async def websocket_off_fetch( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: Dict[str, Any], +) -> None: + """Proxy OpenFoodFacts requests through HA to avoid browser CORS restrictions.""" + from homeassistant.helpers.aiohttp_client import async_get_clientsession + from aiohttp import ClientTimeout + + session = async_get_clientsession(hass) + headers = {"User-Agent": "HomeAssistant/ShoppingListManager (contact@homeassistant.io)"} + + try: + if msg.get("barcode"): + barcode = msg["barcode"] + fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price" + url = f"https://world.openfoodfacts.org/api/v2/product/{barcode}.json?fields={fields}" + async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp: + if not resp.ok: + connection.send_result(msg["id"], {"status": 0}) + return + data = await resp.json(content_type=None) + connection.send_result(msg["id"], { + "status": data.get("status", 0), + "product": data.get("product"), + }) + else: + query = msg.get("query", "") + page_size = msg.get("page_size", 5) + fields = "product_name,categories_tags,image_front_thumb_url,image_front_url,image_url,price" + url = ( + f"https://world.openfoodfacts.org/api/v2/search" + f"?search_terms={query}&fields={fields}&page_size={page_size}" + ) + async with session.get(url, timeout=ClientTimeout(total=10), headers=headers) as resp: + if not resp.ok: + connection.send_result(msg["id"], {"products": []}) + return + data = await resp.json(content_type=None) + connection.send_result(msg["id"], {"products": data.get("products", [])}) + except Exception as err: + _LOGGER.warning("OpenFoodFacts proxy request failed: %s", err) + connection.send_error(msg["id"], "fetch_failed", str(err)) + + # ============================================================================= # INTEGRATION SETTINGS HANDLERS # =============================================================================