diff --git a/src/components/ha-hls-player.ts b/src/components/ha-hls-player.ts index 03f28385fd..fd16491efa 100644 --- a/src/components/ha-hls-player.ts +++ b/src/components/ha-hls-player.ts @@ -9,7 +9,6 @@ import { } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { nextRender } from "../common/util/render-status"; -import { getExternalConfig } from "../external_app/external_config"; import type { HomeAssistant } from "../types"; import "./ha-alert"; @@ -91,18 +90,9 @@ class HaHLSPlayer extends LitElement { this._startHls(); } - private async _getUseExoPlayer(): Promise { - if (!this.hass!.auth.external || !this.allowExoPlayer) { - return false; - } - const externalConfig = await getExternalConfig(this.hass!.auth.external); - return externalConfig && externalConfig.hasExoPlayer; - } - private async _startHls(): Promise { this._error = undefined; - const useExoPlayerPromise = this._getUseExoPlayer(); const masterPlaylistPromise = fetch(this.url); const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min")) @@ -126,7 +116,8 @@ class HaHLSPlayer extends LitElement { return; } - const useExoPlayer = await useExoPlayerPromise; + const useExoPlayer = + this.allowExoPlayer && this.hass.auth.external?.config.hasExoPlayer; const masterPlaylist = await (await masterPlaylistPromise).text(); if (!this.isConnected) { diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 6d19184f88..70ee6ed471 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -44,10 +44,6 @@ import { PersistentNotification, subscribeNotifications, } from "../data/persistent_notification"; -import { - ExternalConfig, - getExternalConfig, -} from "../external_app/external_config"; import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive"; import { haStyleScrollbar } from "../resources/styles"; import type { HomeAssistant, PanelInfo, Route } from "../types"; @@ -192,8 +188,6 @@ class HaSidebar extends LitElement { @property({ type: Boolean }) public editMode = false; - @state() private _externalConfig?: ExternalConfig; - @state() private _notifications?: PersistentNotification[]; @state() private _renderEmptySortable = false; @@ -270,13 +264,6 @@ class HaSidebar extends LitElement { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - - if (this.hass && this.hass.auth.external) { - getExternalConfig(this.hass.auth.external).then((conf) => { - this._externalConfig = conf; - }); - } - subscribeNotifications(this.hass.connection, (notifications) => { this._notifications = notifications; }); @@ -559,8 +546,7 @@ class HaSidebar extends LitElement { private _renderExternalConfiguration() { return html`${!this.hass.user?.is_admin && - this._externalConfig && - this._externalConfig.hasSettingsScreen + this.hass.auth.external?.config.hasSettingsScreen ? html` { + window.addEventListener("haptic", (ev) => + hassMainEl.hass.auth.external!.fireMessage({ + type: "haptic", + payload: { hapticType: ev.detail }, + }) + ); + + hassMainEl.hass.auth.external!.addCommandHandler((msg) => + handleExternalMessage(hassMainEl, msg) + ); +}; + +const handleExternalMessage = ( + hassMainEl: HomeAssistantMain, + msg: EMExternalMessageCommands +): boolean => { + const bus = hassMainEl.hass.auth.external!; + + if (msg.command === "restart") { + hassMainEl.hass.connection.reconnect(true); + bus.fireMessage({ + id: msg.id, + type: "result", + success: true, + result: null, + }); + } else if (msg.command === "notifications/show") { + fireEvent(hassMainEl, "hass-show-notifications"); + bus.fireMessage({ + id: msg.id, + type: "result", + success: true, + result: null, + }); + } else { + return false; + } + + return true; +}; diff --git a/src/external_app/external_auth.ts b/src/external_app/external_auth.ts index 1917e4bec2..26b1f1f892 100644 --- a/src/external_app/external_auth.ts +++ b/src/external_app/external_auth.ts @@ -128,14 +128,14 @@ export class ExternalAuth extends Auth { } } -export const createExternalAuth = (hassUrl: string) => { +export const createExternalAuth = async (hassUrl: string) => { const auth = new ExternalAuth(hassUrl); if ( (window.externalApp && window.externalApp.externalBus) || (window.webkit && window.webkit.messageHandlers.externalBus) ) { auth.external = new ExternalMessaging(); - auth.external.attach(); + await auth.external.attach(); } return auth; }; diff --git a/src/external_app/external_config.ts b/src/external_app/external_config.ts deleted file mode 100644 index 27728deaf0..0000000000 --- a/src/external_app/external_config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ExternalMessaging } from "./external_messaging"; - -export interface ExternalConfig { - hasSettingsScreen: boolean; - canWriteTag: boolean; - hasExoPlayer: boolean; -} - -export const getExternalConfig = ( - bus: ExternalMessaging -): Promise => { - if (!bus.cache.cfg) { - bus.cache.cfg = bus.sendMessage({ - type: "config/get", - }); - } - return bus.cache.cfg; -}; diff --git a/src/external_app/external_events_forwarder.ts b/src/external_app/external_events_forwarder.ts deleted file mode 100644 index 4ac2258105..0000000000 --- a/src/external_app/external_events_forwarder.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ExternalMessaging } from "./external_messaging"; - -export const externalForwardConnectionEvents = (bus: ExternalMessaging) => { - window.addEventListener("connection-status", (ev) => - bus.fireMessage({ - type: "connection-status", - payload: { event: ev.detail }, - }) - ); -}; - -export const externalForwardHaptics = (bus: ExternalMessaging) => - window.addEventListener("haptic", (ev) => - bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } }) - ); diff --git a/src/external_app/external_messaging.ts b/src/external_app/external_messaging.ts index 7dd03095cb..fdab1ab2eb 100644 --- a/src/external_app/external_messaging.ts +++ b/src/external_app/external_messaging.ts @@ -1,9 +1,3 @@ -import { Connection } from "home-assistant-js-websocket"; -import { - externalForwardConnectionEvents, - externalForwardHaptics, -} from "./external_events_forwarder"; - const CALLBACK_EXTERNAL_BUS = "externalBus"; interface CommandInFlight { @@ -42,24 +36,54 @@ interface EMExternalMessageRestart { command: "restart"; } +interface EMExternMessageShowNotifications { + id: number; + type: "command"; + command: "notifications/show"; +} + +export type EMExternalMessageCommands = + | EMExternalMessageRestart + | EMExternMessageShowNotifications; + type ExternalMessage = | EMMessageResultSuccess | EMMessageResultError - | EMExternalMessageRestart; + | EMExternalMessageCommands; + +type ExternalMessageHandler = (msg: EMExternalMessageCommands) => boolean; + +export interface ExternalConfig { + hasSettingsScreen: boolean; + hasSidebar: boolean; + canWriteTag: boolean; + hasExoPlayer: boolean; +} export class ExternalMessaging { + public config!: ExternalConfig; + public commands: { [msgId: number]: CommandInFlight } = {}; - public connection?: Connection; - - public cache: Record = {}; - public msgId = 0; - public attach() { - externalForwardConnectionEvents(this); - externalForwardHaptics(this); + private _commandHandler?: ExternalMessageHandler; + + public async attach() { window[CALLBACK_EXTERNAL_BUS] = (msg) => this.receiveMessage(msg); + window.addEventListener("connection-status", (ev) => + this.fireMessage({ + type: "connection-status", + payload: { event: ev.detail }, + }) + ); + this.config = await this.sendMessage({ + type: "config/get", + }); + } + + public addCommandHandler(handler: ExternalMessageHandler) { + this._commandHandler = handler; } /** @@ -97,36 +121,25 @@ export class ExternalMessaging { } if (msg.type === "command") { - if (!this.connection) { + if (!this._commandHandler || !this._commandHandler(msg)) { + let code: string; + let message: string; + if (this._commandHandler) { + code = "not_ready"; + message = "Command handler not ready"; + } else { + code = "unknown_command"; + message = `Unknown command ${msg.command}`; + } // eslint-disable-next-line no-console - console.warn("Received command without having connection set", msg); + console.warn(message, msg); this.fireMessage({ id: msg.id, type: "result", success: false, error: { - code: "commands_not_init", - message: `Commands connection not set`, - }, - }); - } else if (msg.command === "restart") { - this.connection.reconnect(true); - this.fireMessage({ - id: msg.id, - type: "result", - success: true, - result: null, - }); - } else { - // eslint-disable-next-line no-console - console.warn("Received unknown command", msg.command, msg); - this.fireMessage({ - id: msg.id, - type: "result", - success: false, - error: { - code: "unknown_command", - message: `Unknown command ${msg.command}`, + code, + message, }, }); } diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index 39339c4486..1b65f33de3 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -38,7 +38,7 @@ interface EditSideBarEvent { } @customElement("home-assistant-main") -class HomeAssistantMain extends LitElement { +export class HomeAssistantMain extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public route?: Route; @@ -47,6 +47,8 @@ class HomeAssistantMain extends LitElement { @state() private _sidebarEditMode = false; + @state() private _externalSidebar = false; + constructor() { super(); listenMediaQuery("(max-width: 870px)", (matches) => { @@ -56,11 +58,12 @@ class HomeAssistantMain extends LitElement { protected render(): TemplateResult { const hass = this.hass; - const sidebarNarrow = this._sidebarNarrow; + const sidebarNarrow = this._sidebarNarrow || this._externalSidebar; const disableSwipe = this._sidebarEditMode || !sidebarNarrow || - NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1; + NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1 || + this._externalSidebar; // Style block in render because of the mixin that is not supported return html` @@ -107,6 +110,14 @@ class HomeAssistantMain extends LitElement { protected firstUpdated() { import(/* webpackPreload: true */ "../components/ha-sidebar"); + if (this.hass.auth.external) { + this._externalSidebar = + this.hass.auth.external.config.hasSidebar === true; + import("../external_app/external_app_entrypoint").then((mod) => + mod.attachExternalToApp(this) + ); + } + this.addEventListener( "hass-edit-sidebar", (ev: HASSDomEvent) => { @@ -129,6 +140,12 @@ class HomeAssistantMain extends LitElement { if (this._sidebarEditMode) { return; } + if (this._externalSidebar) { + this.hass.auth.external!.fireMessage({ + type: "sidebar/show", + }); + return; + } if (this._sidebarNarrow) { if (this.drawer.opened) { this.drawer.close(); diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 7668df18fa..2db813c397 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -3,15 +3,8 @@ import "@material/mwc-list/mwc-list-item"; import type { ActionDetail } from "@material/mwc-list"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; @@ -24,10 +17,6 @@ import { SupervisorAvailableUpdates, } from "../../../data/supervisor/root"; import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar"; -import { - ExternalConfig, - getExternalConfig, -} from "../../../external_app/external_config"; import "../../../layouts/ha-app-layout"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; @@ -54,18 +43,6 @@ class HaConfigDashboard extends LitElement { @property() public showAdvanced!: boolean; - @state() private _externalConfig?: ExternalConfig; - - protected firstUpdated(changedProps: PropertyValues) { - super.firstUpdated(changedProps); - - if (this.hass && this.hass.auth.external) { - getExternalConfig(this.hass.auth.external).then((conf) => { - this._externalConfig = conf; - }); - } - } - protected render(): TemplateResult { return html` @@ -143,7 +120,6 @@ class HaConfigDashboard extends LitElement { diff --git a/src/panels/config/dashboard/ha-config-navigation.ts b/src/panels/config/dashboard/ha-config-navigation.ts index 66c47ccb64..d457429318 100644 --- a/src/panels/config/dashboard/ha-config-navigation.ts +++ b/src/panels/config/dashboard/ha-config-navigation.ts @@ -6,7 +6,6 @@ import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; -import { ExternalConfig } from "../../../external_app/external_config"; import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; import { HomeAssistant } from "../../../types"; @@ -20,14 +19,12 @@ class HaConfigNavigation extends LitElement { @property() public pages!: PageNavigation[]; - @property() public externalConfig?: ExternalConfig; - protected render(): TemplateResult { return html` ${this.pages.map((page) => ( page.path === "#external-app-configuration" - ? this.externalConfig?.hasSettingsScreen + ? this.hass.auth.external?.config.hasSettingsScreen : canShowPage(this.hass, page) ) ? html` diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index a8c138ae23..6b53cac199 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -28,7 +28,6 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; -import { getExternalConfig } from "../../../external_app/external_config"; import "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; @@ -53,14 +52,12 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { @state() private _tags: Tag[] = []; - @state() private _canWriteTags = false; + private get _canWriteTags() { + return this.hass.auth.external?.config.canWriteTag; + } private _columns = memoizeOne( - ( - narrow: boolean, - canWriteTags: boolean, - _language - ): DataTableColumnContainer => { + (narrow: boolean, _language): DataTableColumnContainer => { const columns: DataTableColumnContainer = { icon: { title: "", @@ -103,7 +100,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { `, }; } - if (canWriteTags) { + if (this._canWriteTags) { columns.write = { title: "", type: "icon-button", @@ -152,11 +149,6 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { protected firstUpdated(changedProperties: PropertyValues) { super.firstUpdated(changedProperties); this._fetchTags(); - if (this.hass && this.hass.auth.external) { - getExternalConfig(this.hass.auth.external).then((conf) => { - this._canWriteTags = conf.canWriteTag; - }); - } } protected hassSubscribe() { @@ -181,11 +173,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { back-path="/config" .route=${this.route} .tabs=${configSections.tags} - .columns=${this._columns( - this.narrow, - this._canWriteTags, - this.hass.language - )} + .columns=${this._columns(this.narrow, this.hass.language)} .data=${this._data(this._tags)} .noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")} hasFab diff --git a/src/state/external-mixin.ts b/src/state/external-mixin.ts deleted file mode 100644 index 4bc48e5faf..0000000000 --- a/src/state/external-mixin.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Constructor } from "../types"; -import { HassBaseEl } from "./hass-base-mixin"; - -export const ExternalMixin = >( - superClass: T -) => - class extends superClass { - protected hassConnected() { - super.hassConnected(); - - if (this.hass!.auth.external) { - this.hass!.auth.external.connection = this.hass!.connection; - } - } - }; diff --git a/src/state/hass-element.ts b/src/state/hass-element.ts index 1a8a6f3999..7a24238c23 100644 --- a/src/state/hass-element.ts +++ b/src/state/hass-element.ts @@ -6,7 +6,6 @@ import DisconnectToastMixin from "./disconnect-toast-mixin"; import { hapticMixin } from "./haptic-mixin"; import { HassBaseEl } from "./hass-base-mixin"; import { loggingMixin } from "./logging-mixin"; -import { ExternalMixin } from "./external-mixin"; import MoreInfoMixin from "./more-info-mixin"; import NotificationMixin from "./notification-mixin"; import { panelTitleMixin } from "./panel-title-mixin"; @@ -32,5 +31,4 @@ export class HassElement extends ext(HassBaseEl, [ hapticMixin, panelTitleMixin, loggingMixin, - ExternalMixin, ]) {}