home-assistant-js-websocket

repository·master·Indexed 18 days ago

https://github.com/home-assistant/home-assistant-js-websocket

A zero-dependency JavaScript websocket client for Home Assistant. It enables custom applications to retrieve authentication tokens via OAuth2 and communicate with the Home Assistant websocket API to subscribe to entities, configuration, and services, or call services and manage data collections.

Tokens
9.4K
Snippets
31
Records
40
Agent score
63%

What's inside home-assistant-js-websocket

  1. Manage connection reconnection and suspension

    master

    The Connection object automatically attempts to reconnect when the connection is lost and resubscribes to event listeners upon reconnection.

    Suspend Reconnection

    To prevent automatic reconnection until a specific condition is met, pass a promise to suspendReconnectUntil(). Messages sent while suspended will be queued and sent once the connection is re-established. If the first reconnect attempt fails, queued messages are rejected.

    connection.suspendReconnectUntil(
      new Promise((resolve) => {
        // Resolve this to allow reconnection
        resolve();
      }),
    );

    Suspend Connection

    To actively close the connection and wait for a promise to resolve before reconnecting, use suspend(). You can also use suspendReconnectUntil() followed by suspend().

    connection.suspend(
      new Promise((resolve) => {
        resolve();
      }),
    );

    Connection Events

    EventDataDescription
    ready-Fired when authentication is successful and the connection is ready.
    disconnected-Fired when the connection is lost.
    reconnect-errorError codeFired on fatal reconnection errors (e.g., ERR_INVALID_AUTH).

    Closing

    Use connection.close() to close the connection without attempting to reconnect.

  2. Setup for NodeJS environments

    master

    Since NodeJS does not have a built-in WebSocket client, you must provide one (e.g., ws) by polyfilling it into the global namespace before using the library.

    If using TypeScript, ensure you install @types/ws.

    JavaScript:

    globalThis.WebSocket = require("ws");

    TypeScript:

    const wnd = globalThis;
    wnd.WebSocket = require("ws");
  3. Initialize a connection to Home Assistant

    master

    To connect to a Home Assistant instance, you must first obtain an authentication token using getAuth(), then pass that token to createConnection(). The library handles the OAuth2 flow and redirection for you. If getAuth() is called without a hassUrl, it will throw ERR_HASS_HOST_REQUIRED, at which point you should prompt the user for their instance URL and retry.

    import {
      getAuth,
      createConnection,
      subscribeEntities,
      ERR_HASS_HOST_REQUIRED,
    } from "home-assistant-js-websocket";
    
    async function connect() {
      let auth;
      try {
        // Try to pick up authentication after user logs in
        auth = await getAuth();
      } catch (err) {
        if (err === ERR_HASS_HOST_REQUIRED) {
          const hassUrl = prompt(
            "What host to connect to?",
            "http://localhost:8123",
          );
          // Redirect user to log in on their instance
          auth = await getAuth({ hassUrl });
        } else {
          alert(`Unknown error: ${err}`);
          return;
        }
      }
      const connection = await createConnection({ auth });
      subscribeEntities(connection, (ent) => console.log(ent));
    }
    
    connect();
  4. Try out the Home Assistant JS WebSocket client

    master

    To run the local demo and see the client in action, clone the repository and execute the following commands to install dependencies, build the project, and start a local HTTP server:

    1. yarn install to install dependencies.
    2. yarn build to build the project.
    3. npx http-server -o to launch a local server and open your browser.

    Once the browser opens, navigate to example.html to view the demonstration.

    yarn install
    yarn build
    npx http-server -o
    # A browser will open, navigate to example.html
  5. How Connection handles reconnection and suspension

    master

    The Connection class implements an automatic reconnection strategy with several key behaviors:

    Automatic Reconnection

    When a connection is lost, the class attempts to reconnect using an exponential backoff strategy (up to 5 attempts, with delays increasing by 1 second per attempt). If createSocket fails with ERR_INVALID_AUTH, it stops retrying and fires the reconnect-error event.

    Suspension and Queuing

    You can temporarily halt reconnection attempts using suspend() and suspendReconnectUntil(suspendPromise).

    When the connection is in a suspended state (e.g., during a network transition or while waiting for a specific condition):

    1. The socket is closed.
    2. Any calls to sendMessage or sendMessagePromise are placed into an internal _queuedMessages array.
    3. Once the suspendPromise resolves, the connection attempts to reconnect.
    4. Upon successful reconnection, all queued messages are automatically processed in order.
  6. Use long-lived access tokens

    master

    While getAuth() is preferred in browsers for security, you can use long-lived access tokens by creating your own auth object using createLongLivedTokenAuth.

    import {
      createConnection,
      subscribeEntities,
      createLongLivedTokenAuth,
    } from "home-assistant-js-websocket";
    
    (async () => {
      const auth = createLongLivedTokenAuth(
        "http://localhost:8123",
        "YOUR ACCESS TOKEN",
      );
    
      const connection = await createConnection({ auth });
      subscribeEntities(connection, (entities) => console.log(entities));
    })();
  7. Use the Connection API to interact with Home Assistant

    master

    A connection object obtained via createConnection() is the primary interface for interacting with Home Assistant. It provides methods for sending messages, subscribing to events, and accessing connection metadata.

    Key methods:

    • conn.haVersion: A string representing the current Home Assistant version.
    • conn.subscribeEvents(eventCallback, [eventType]): Subscribes to all or specific events on the Home Assistant bus. Returns a promise that resolves to a cancellation function. Subscriptions are automatically re-established on reconnect.
    • conn.sendMessagePromise(message): Sends a message to the server. Returns a promise. If the connection is lost during the operation, it rejects with ERR_CONNECTION_LOST.
    • conn.subscribeMessage(callback, subscribeMessage, [options]): Calls a Home Assistant endpoint that creates a subscription. Returns a promise that resolves to a cancellation function. Subscriptions re-establish on reconnect unless options.resubscribe is set to false.
  8. Use the Auth API to manage credentials

    master

    The getAuth() method returns an Auth instance used to manage authentication state.

    Properties:

    • wsUrl: The websocket URL of the instance.
    • accessToken: The current access token.
    • expired: A boolean indicating if the access token has expired.

    Methods:

    • auth.refreshAccessToken(): Fetches a new access token from the server.
    • auth.revoke(): Revokes the refresh token and all related access tokens. Returns a promise that resolves when the request is finished.

    Handling Invalid Auth: If you are caching tokens and they become invalid, createConnection will reject with ERR_INVALID_AUTH. In this case, clear your cache (e.g., storeTokens(null)) and call getAuth() again to trigger the standard auth flow.

  9. Use getAuth() to obtain authentication

    master

    getAuth() manages the OAuth2 flow to fetch an authentication token. It handles redirecting the user to the Home Assistant instance and fetching the token after a successful login.

    Options

    OptionDescription
    hassUrlThe URL of the Home Assistant instance. Required for the initial redirect.
    clientIdClient ID (defaults to current page domain). Pass null for system users.
    redirectUrlURL to redirect back to after login (defaults to current page).
    saveTokensFunction to store token information.
    loadTokensFunction returning a promise that resolves to stored token info.
    authCodeAn auth code received via other means to bypass the standard OAuth2 flow.
    limitHassInstanceIf true, restricts credentials to the provided hassUrl and clientId.

    Errors

    ErrorDescription
    ERR_HASS_HOST_REQUIREDhassUrl must be provided to continue.
    ERR_INVALID_AUTHThe URL contains an invalid or expired authorization code.
    ERR_INVALID_HTTPS_TO_HTTPAttempting to fetch tokens from an http instance from a secure https context.
    ERR_INVALID_AUTH_CALLBACKThe clientId or hassUrl in the callback does not match expected values (when limitHassInstance is used).
    getAuth({ hassUrl: "http://localhost:8123" });
  10. Subscribe to Entities, Config, or Services

    master

    You can subscribe to real-time updates for Home Assistant entities, configuration, or services using subscription functions. These functions return an unsubscribe function to stop listening.

    Entities

    Use subscribeEntities to receive a callback whenever entities are loaded or their states change. The callback receives an object keyed by entity_id.

    import { subscribeEntities } from "home-assistant-js-websocket";
    subscribeEntities(conn, (entities) => console.log("New entities!", entities));

    Alternatively, use entitiesColl for a collection-based approach:

    import { entitiesColl } from "home-assistant-js-websocket";
    const coll = entitiesColl(conn);
    console.log(coll.state);
    await coll.refresh();
    coll.subscribe((entities) => console.log(entities));

    Config

    Use subscribeConfig to listen for changes in Home Assistant configuration.

    import { subscribeConfig } from "home-assistant-js-websocket";
    subscribeConfig(conn, (config) => console.log("New config!", config));

    Services

    Use subscribeServices to listen for changes in available services.

    import { subscribeServices } from "home-assistant-js-websocket";
    subscribeServices(conn, (services) => console.log("New services!", services));
  11. Create custom data collections with getCollection()

    master

    getCollection allows you to create a managed data store that handles initial fetching, automatic updates via event subscriptions, and shared listeners. This is useful for pre-loading data before a UI renders.

    API Signature

    getCollection<State>(
      conn: Connection,
      key: string,
      fetchCollection: (conn: Connection) => Promise<State>,
      subscribeUpdates: (
        conn: Connection,
        store: Store<State>
      ) => Promise<UnsubscribeFunc>,
    ): Collection<State>

    Collection Interface

    • state: State: The current state of the collection.
    • async refresh(): Promise<void>: Manually triggers a refresh of the data.
    • subscribe(subscriber: (state: State) => void): UnsubscribeFunc: Adds a listener to the collection state.

    Example: Creating a Panels Collection

    import { getCollection } from "home-assistant-js-websocket";
    
    function panelRegistered(state, event) {
      if (state === undefined) return null;
      return {
        panels: state.panels.concat(event.data.panel),
      };
    }
    
    const fetchPanels = (conn) => conn.sendMessagePromise({ type: "get_panels" });
    const subscribeUpdates = (conn, store) =>
      conn.subscribeEvents(store.action(panelRegistered), "panel_registered");
    
    const panelsColl = getCollection(conn, "_pnl", fetchPanels, subscribeUpdates);
    
    // Usage
    console.log(panelsColl.state);
    await panelsColl.refresh();
    panelsColl.subscribe((panels) => console.log("New panels!", panels));