Skip to content

Commit

Permalink
Add websocket API (home-assistant#4582)
Browse files Browse the repository at this point in the history
* Add websocket API

* Add identifiers to interactions

* Allow unsubscribing event listeners

* Add support for fetching data

* Clean up handling code websockets api

* Lint

* Add Home Assistant version to auth messages

* Py.test be less verbose in tox
  • Loading branch information
balloob authored Nov 27, 2016
1 parent 03e0c7c commit 914a868
Show file tree
Hide file tree
Showing 7 changed files with 831 additions and 14 deletions.
125 changes: 125 additions & 0 deletions homeassistant/components/frontend/www_static/websocket_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket debug</title>
<style>
.controls {
display: flex;
flex-direction: row;
}

.controls textarea {
height: 160px;
min-width: 400px;
margin-right: 24px;
}
</style>
</head>
<body>
<div class='controls'>
<textarea id="messageinput">
{
"id": 1, "type": "subscribe_events", "event_type": "state_changed"
}
</textarea>
<pre>
Examples:
{
"id": 2, "type": "subscribe_events", "event_type": "state_changed"
}

{
"id": 3, "type": "call_service", "domain": "light", "service": "turn_off"
}

{
"id": 4, "type": "unsubscribe_events", "subscription": 2
}

{
"id": 5, "type": "get_states"
}

{
"id": 6, "type": "get_config"
}

{
"id": 7, "type": "get_services"
}

{
"id": 8, "type": "get_panels"
}
</pre>
</div>
<div>
<button type="button" onclick="openSocket();" >Open</button>
<button type="button" onclick="send();" >Send</button>
<button type="button" onclick="closeSocket();" >Close</button>
</div>
<!-- Server responses get written here -->
<pre id="messages"></pre>

<!-- Script to utilise the WebSocket -->
<script type="text/javascript">
var webSocket;
var messages = document.getElementById("messages");

function openSocket(){
var isOpen = false;
// Ensures only one connection is open at a time
if(webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED){
writeResponse("WebSocket is already opened.");
return;
}
// Create a new instance of the websocket
webSocket = new WebSocket("ws://localhost:8123/api/websocket");

/**
* Binds functions to the listeners for the websocket.
*/
webSocket.onopen = function(event){
if (!isOpen) {
isOpen = true;
writeResponse('Connection opened');
}
// For reasons I can't determine, onopen gets called twice
// and the first time event.data is undefined.
// Leave a comment if you know the answer.
if(event.data === undefined)
return;

writeResponse(event.data);
};

webSocket.onmessage = function(event){
writeResponse(event.data);
};

webSocket.onclose = function(event){
writeResponse("Connection closed");
};
}

/**
* Sends the value of the text input to the server
*/
function send(){
var text = document.getElementById("messageinput").value;
webSocket.send(text);
}

function closeSocket(){
webSocket.close();
}

function writeResponse(text){
messages.innerHTML += "\n" + text;
}

openSocket();
</script>
</body>
</html>
17 changes: 11 additions & 6 deletions homeassistant/components/http/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,17 @@ def no_auth_middleware_handler(request):
@asyncio.coroutine
def auth_middleware_handler(request):
"""Auth middleware to check authentication."""
hass = app['hass']

# Auth code verbose on purpose
authenticated = False

if hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''),
hass.http.api_password):
if (HTTP_HEADER_HA_AUTH in request.headers and
validate_password(request,
request.headers[HTTP_HEADER_HA_AUTH])):
# A valid auth header has been set
authenticated = True

elif hmac.compare_digest(request.GET.get(DATA_API_PASSWORD, ''),
hass.http.api_password):
elif (DATA_API_PASSWORD in request.GET and
validate_password(request, request.GET[DATA_API_PASSWORD])):
authenticated = True

elif is_trusted_ip(request):
Expand All @@ -59,3 +58,9 @@ def is_trusted_ip(request):
return ip_addr and any(
ip_addr in trusted_network for trusted_network
in request.app[KEY_TRUSTED_NETWORKS])


def validate_password(request, api_password):
"""Test if password is valid."""
return hmac.compare_digest(api_password,
request.app['hass'].http.api_password)
Loading

0 comments on commit 914a868

Please sign in to comment.