Home Assistant Unofficial Reference 2024.12.1
websocket_api.py
Go to the documentation of this file.
1 """Websocekt API handlers for the hassio integration."""
2 
3 import logging
4 from numbers import Number
5 import re
6 from typing import Any
7 
8 import voluptuous as vol
9 
10 from homeassistant.components import websocket_api
11 from homeassistant.components.websocket_api import ActiveConnection
12 from homeassistant.core import HomeAssistant, callback
13 from homeassistant.exceptions import Unauthorized
16  async_dispatcher_connect,
17  async_dispatcher_send,
18 )
19 
20 from . import HassioAPIError
21 from .const import (
22  ATTR_DATA,
23  ATTR_ENDPOINT,
24  ATTR_METHOD,
25  ATTR_SESSION_DATA_USER_ID,
26  ATTR_TIMEOUT,
27  ATTR_WS_EVENT,
28  DOMAIN,
29  EVENT_SUPERVISOR_EVENT,
30  WS_ID,
31  WS_TYPE,
32  WS_TYPE_API,
33  WS_TYPE_EVENT,
34  WS_TYPE_SUBSCRIBE,
35 )
36 from .handler import HassIO
37 
38 SCHEMA_WEBSOCKET_EVENT = vol.Schema(
39  {vol.Required(ATTR_WS_EVENT): cv.string},
40  extra=vol.ALLOW_EXTRA,
41 )
42 
43 # Endpoints needed for ingress can't require admin because addons can set `panel_admin: false`
44 # fmt: off
45 WS_NO_ADMIN_ENDPOINTS = re.compile(
46  r"^(?:"
47  r"|/ingress/(session|validate_session)"
48  r"|/addons/[^/]+/info"
49  r")$"
50 )
51 # fmt: on
52 
53 _LOGGER: logging.Logger = logging.getLogger(__package__)
54 
55 
56 @callback
57 def async_load_websocket_api(hass: HomeAssistant) -> None:
58  """Set up the websocket API."""
59  websocket_api.async_register_command(hass, websocket_supervisor_event)
60  websocket_api.async_register_command(hass, websocket_supervisor_api)
61  websocket_api.async_register_command(hass, websocket_subscribe)
62 
63 
64 @callback
65 @websocket_api.require_admin
66 @websocket_api.websocket_command({vol.Required(WS_TYPE): WS_TYPE_SUBSCRIBE})
68  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
69 ) -> None:
70  """Subscribe to supervisor events."""
71 
72  @callback
73  def forward_messages(data: dict[str, str]) -> None:
74  """Forward events to websocket."""
75  connection.send_message(websocket_api.event_message(msg[WS_ID], data))
76 
77  connection.subscriptions[msg[WS_ID]] = async_dispatcher_connect(
78  hass, EVENT_SUPERVISOR_EVENT, forward_messages
79  )
80  connection.send_message(websocket_api.result_message(msg[WS_ID]))
81 
82 
83 @callback
84 @websocket_api.websocket_command( { vol.Required(WS_TYPE): WS_TYPE_EVENT,
85  vol.Required(ATTR_DATA): SCHEMA_WEBSOCKET_EVENT,
86  }
87 )
89  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
90 ) -> None:
91  """Publish events from the Supervisor."""
92  connection.send_result(msg[WS_ID])
93  async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA])
94 
95 
96 @websocket_api.websocket_command( { vol.Required(WS_TYPE): WS_TYPE_API,
97  vol.Required(ATTR_ENDPOINT): cv.string,
98  vol.Required(ATTR_METHOD): cv.string,
99  vol.Optional(ATTR_DATA): dict,
100  vol.Optional(ATTR_TIMEOUT): vol.Any(Number, None),
101  }
102 )
103 @websocket_api.async_response
104 async def websocket_supervisor_api(
105  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
106 ) -> None:
107  """Websocket handler to call Supervisor API."""
108  if not connection.user.is_admin and not WS_NO_ADMIN_ENDPOINTS.match(
109  msg[ATTR_ENDPOINT]
110  ):
111  raise Unauthorized
112  supervisor: HassIO = hass.data[DOMAIN]
113 
114  command = msg[ATTR_ENDPOINT]
115  payload = msg.get(ATTR_DATA, {})
116 
117  if command == "/ingress/session":
118  # Send user ID on session creation, so the supervisor can correlate session tokens with users
119  # for every request that is authenticated with the given ingress session token.
120  payload[ATTR_SESSION_DATA_USER_ID] = connection.user.id
121 
122  try:
123  result = await supervisor.send_command(
124  command,
125  method=msg[ATTR_METHOD],
126  timeout=msg.get(ATTR_TIMEOUT, 10),
127  payload=payload,
128  source="core.websocket_api",
129  )
130  except HassioAPIError as err:
131  _LOGGER.error("Failed to to call %s - %s", msg[ATTR_ENDPOINT], err)
132  connection.send_error(
133  msg[WS_ID], code=websocket_api.ERR_UNKNOWN_ERROR, message=str(err)
134  )
135  else:
136  connection.send_result(msg[WS_ID], result.get(ATTR_DATA, {}))
137 
None websocket_supervisor_api(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None websocket_subscribe(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None websocket_supervisor_event(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None async_load_websocket_api(HomeAssistant hass)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193