1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-24 20:55:49 +00:00
Files
frontend/src/state/connection-mixin.ts
2021-05-20 16:23:53 +02:00

205 lines
6.5 KiB
TypeScript

import {
Auth,
callService,
Connection,
ERR_CONNECTION_LOST,
ERR_INVALID_AUTH,
HassConfig,
subscribeConfig,
subscribeEntities,
subscribeServices,
} from "home-assistant-js-websocket";
import { fireEvent } from "../common/dom/fire_event";
import { broadcastConnectionStatus } from "../data/connection-status";
import { subscribeFrontendUserData } from "../data/frontend";
import { forwardHaptic } from "../data/haptics";
import { DEFAULT_PANEL } from "../data/panel";
import { serviceCallWillDisconnect } from "../data/service";
import { NumberFormat, TimeFormat } from "../data/translation";
import { subscribePanels } from "../data/ws-panels";
import { translationMetadata } from "../resources/translations-metadata";
import { Constructor, ServiceCallResponse } from "../types";
import { fetchWithAuth } from "../util/fetch-with-auth";
import { getState } from "../util/ha-pref-storage";
import hassCallApi from "../util/hass-call-api";
import { getLocalLanguage } from "../util/hass-translation";
import { HassBaseEl } from "./hass-base-mixin";
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
) =>
class extends superClass {
protected initializeHass(auth: Auth, conn: Connection) {
const language = getLocalLanguage();
this.hass = {
auth,
connection: conn,
connected: true,
states: null as any,
config: null as any,
themes: null as any,
panels: null as any,
services: null as any,
user: null as any,
panelUrl: (this as any)._panelUrl,
defaultPanel: DEFAULT_PANEL,
language,
selectedLanguage: null,
locale: {
language,
number_format: NumberFormat.language,
time_format: TimeFormat.language,
},
resources: null as any,
localize: () => "",
translationMetadata,
dockedSidebar: "docked",
vibrate: true,
suspendWhenHidden: true,
enableShortcuts: true,
moreInfoEntityId: null,
hassUrl: (path = "") => new URL(path, auth.data.hassUrl).toString(),
callService: async (domain, service, serviceData = {}, target) => {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log(
"Calling service",
domain,
service,
serviceData,
target
);
}
try {
return (await callService(
conn,
domain,
service,
serviceData,
target
)) as ServiceCallResponse;
} catch (err) {
if (
err.error?.code === ERR_CONNECTION_LOST &&
serviceCallWillDisconnect(domain, service)
) {
return { context: { id: "" } };
}
if (__DEV__) {
// eslint-disable-next-line no-console
console.error(
"Error calling service",
domain,
service,
serviceData,
target,
err
);
}
forwardHaptic("failure");
const message =
(this as any).hass.localize(
"ui.notification_toast.service_call_failed",
"service",
`${domain}/${service}`
) + ` ${err.message}`;
fireEvent(this as any, "hass-notification", { message });
throw err;
}
},
callApi: async (method, path, parameters, headers) =>
hassCallApi(auth, method, path, parameters, headers),
fetchWithAuth: (
path: string,
init: Parameters<typeof fetchWithAuth>[2]
) => fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
// For messages that do not get a response
sendWS: (msg) => {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Sending", msg);
}
conn.sendMessage(msg);
},
// For messages that expect a response
callWS: <R>(msg) => {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Sending", msg);
}
const resp = conn.sendMessagePromise<R>(msg);
if (__DEV__) {
resp.then(
// eslint-disable-next-line no-console
(result) => console.log("Received", result),
// eslint-disable-next-line no-console
(err) => console.error("Error", err)
);
}
return resp;
},
loadBackendTranslation: (category, integration?, configFlow?) =>
// @ts-ignore
this._loadHassTranslations(
this.hass?.language,
category,
integration,
configFlow
),
...getState(),
...this._pendingHass,
};
this.hassConnected();
}
protected hassConnected() {
super.hassConnected();
const conn = this.hass!.connection;
broadcastConnectionStatus("connected");
conn.addEventListener("ready", () => this.hassReconnected());
conn.addEventListener("disconnected", () => this.hassDisconnected());
// If we reconnect after losing connection and auth is no longer valid.
conn.addEventListener("reconnect-error", (_conn, err) => {
if (err === ERR_INVALID_AUTH) {
broadcastConnectionStatus("auth-invalid");
location.reload();
}
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeConfig(conn, (config) => this._updateHass({ config }));
subscribeServices(conn, (services) => this._updateHass({ services }));
subscribePanels(conn, (panels) => this._updateHass({ panels }));
subscribeFrontendUserData(conn, "core", (userData) =>
this._updateHass({ userData })
);
}
protected hassReconnected() {
super.hassReconnected();
this._updateHass({ connected: true });
broadcastConnectionStatus("connected");
// on reconnect always fetch config as we might miss an update while we were disconnected
// @ts-ignore
this.hass!.callWS({ type: "get_config" }).then((config: HassConfig) => {
this._updateHass({ config });
});
}
protected hassDisconnected() {
super.hassDisconnected();
this._updateHass({ connected: false });
broadcastConnectionStatus("disconnected");
}
};