Allow trigger reconnect from external bus (#10819)

This commit is contained in:
Paulus Schoutsen 2021-12-09 13:30:20 -08:00 committed by GitHub
parent 149f381bc3
commit 39774c0e02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 15 deletions

View File

@ -43,6 +43,7 @@ import {
PersistentNotification, PersistentNotification,
subscribeNotifications, subscribeNotifications,
} from "../data/persistent_notification"; } from "../data/persistent_notification";
import { getExternalConfig } from "../external_app/external_config";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive"; import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { haStyleScrollbar } from "../resources/styles"; import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, PanelInfo, Route } from "../types"; import type { HomeAssistant, PanelInfo, Route } from "../types";
@ -266,6 +267,11 @@ class HaSidebar extends LitElement {
subscribeNotifications(this.hass.connection, (notifications) => { subscribeNotifications(this.hass.connection, (notifications) => {
this._notifications = notifications; this._notifications = notifications;
}); });
// Temporary workaround for a bug in Android. Can be removed in Home Assistant 2022.2
if (this.hass.auth.external) {
getExternalConfig(this.hass.auth.external);
}
} }
protected updated(changedProps) { protected updated(changedProps) {

View File

@ -2,7 +2,7 @@
* Auth class that connects to a native app for authentication. * Auth class that connects to a native app for authentication.
*/ */
import { Auth } from "home-assistant-js-websocket"; import { Auth } from "home-assistant-js-websocket";
import { ExternalMessaging, InternalMessage } from "./external_messaging"; import { ExternalMessaging, EMMessage } from "./external_messaging";
const CALLBACK_SET_TOKEN = "externalAuthSetToken"; const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken"; const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
@ -36,7 +36,7 @@ declare global {
postMessage(payload: BasePayload); postMessage(payload: BasePayload);
}; };
externalBus: { externalBus: {
postMessage(payload: InternalMessage); postMessage(payload: EMMessage);
}; };
}; };
}; };

View File

@ -1,3 +1,4 @@
import { Connection } from "home-assistant-js-websocket";
import { import {
externalForwardConnectionEvents, externalForwardConnectionEvents,
externalForwardHaptics, externalForwardHaptics,
@ -7,39 +8,50 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
interface CommandInFlight { interface CommandInFlight {
resolve: (data: any) => void; resolve: (data: any) => void;
reject: (err: ExternalError) => void; reject: (err: EMError) => void;
} }
export interface InternalMessage { export interface EMMessage {
id?: number; id?: number;
type: string; type: string;
payload?: unknown; payload?: unknown;
} }
interface ExternalError { interface EMError {
code: string; code: string;
message: string; message: string;
} }
interface ExternalMessageResult { interface EMMessageResultSuccess {
id: number; id: number;
type: "result"; type: "result";
success: true; success: true;
result: unknown; result: unknown;
} }
interface ExternalMessageResultError { interface EMMessageResultError {
id: number; id: number;
type: "result"; type: "result";
success: false; success: false;
error: ExternalError; error: EMError;
} }
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError; interface EMExternalMessageRestart {
id: number;
type: "command";
command: "restart";
}
type ExternalMessage =
| EMMessageResultSuccess
| EMMessageResultError
| EMExternalMessageRestart;
export class ExternalMessaging { export class ExternalMessaging {
public commands: { [msgId: number]: CommandInFlight } = {}; public commands: { [msgId: number]: CommandInFlight } = {};
public connection?: Connection;
public cache: Record<string, any> = {}; public cache: Record<string, any> = {};
public msgId = 0; public msgId = 0;
@ -54,7 +66,7 @@ export class ExternalMessaging {
* Send message to external app that expects a response. * Send message to external app that expects a response.
* @param msg message to send * @param msg message to send
*/ */
public sendMessage<T>(msg: InternalMessage): Promise<T> { public sendMessage<T>(msg: EMMessage): Promise<T> {
const msgId = ++this.msgId; const msgId = ++this.msgId;
msg.id = msgId; msg.id = msgId;
@ -69,7 +81,9 @@ export class ExternalMessaging {
* Send message to external app without expecting a response. * Send message to external app without expecting a response.
* @param msg message to send * @param msg message to send
*/ */
public fireMessage(msg: InternalMessage) { public fireMessage(
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
) {
if (!msg.id) { if (!msg.id) {
msg.id = ++this.msgId; msg.id = ++this.msgId;
} }
@ -82,6 +96,43 @@ export class ExternalMessaging {
console.log("Receiving message from external app", msg); console.log("Receiving message from external app", msg);
} }
if (msg.type === "command") {
if (!this.connection) {
// eslint-disable-next-line no-console
console.warn("Received command without having connection set", 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.socket.close();
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}`,
},
});
}
return;
}
const pendingCmd = this.commands[msg.id]; const pendingCmd = this.commands[msg.id];
if (!pendingCmd) { if (!pendingCmd) {
@ -99,7 +150,7 @@ export class ExternalMessaging {
} }
} }
protected _sendExternal(msg: InternalMessage) { protected _sendExternal(msg: EMMessage) {
if (__DEV__) { if (__DEV__) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("Sending message to external app", msg); console.log("Sending message to external app", msg);

View File

@ -0,0 +1,15 @@
import { Constructor } from "../types";
import { HassBaseEl } from "./hass-base-mixin";
export const ExternalMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
) =>
class extends superClass {
protected hassConnected() {
super.hassConnected();
if (this.hass!.auth.external) {
this.hass!.auth.external.connection = this.hass!.connection;
}
}
};

View File

@ -6,6 +6,7 @@ import DisconnectToastMixin from "./disconnect-toast-mixin";
import { hapticMixin } from "./haptic-mixin"; import { hapticMixin } from "./haptic-mixin";
import { HassBaseEl } from "./hass-base-mixin"; import { HassBaseEl } from "./hass-base-mixin";
import { loggingMixin } from "./logging-mixin"; import { loggingMixin } from "./logging-mixin";
import { ExternalMixin } from "./external-mixin";
import MoreInfoMixin from "./more-info-mixin"; import MoreInfoMixin from "./more-info-mixin";
import NotificationMixin from "./notification-mixin"; import NotificationMixin from "./notification-mixin";
import { panelTitleMixin } from "./panel-title-mixin"; import { panelTitleMixin } from "./panel-title-mixin";
@ -31,4 +32,5 @@ export class HassElement extends ext(HassBaseEl, [
hapticMixin, hapticMixin,
panelTitleMixin, panelTitleMixin,
loggingMixin, loggingMixin,
ExternalMixin,
]) {} ]) {}

View File

@ -131,5 +131,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
(themeMeta.getAttribute("default-content") as string); (themeMeta.getAttribute("default-content") as string);
themeMeta.setAttribute("content", themeColor); themeMeta.setAttribute("content", themeColor);
} }
this.hass!.auth.external?.fireMessage({ type: "theme-update" });
} }
}; };

View File

@ -2,16 +2,16 @@ import { assert } from "chai";
import { import {
ExternalMessaging, ExternalMessaging,
InternalMessage, EMMessage,
} from "../../src/external_app/external_messaging"; } from "../../src/external_app/external_messaging";
// @ts-ignore // @ts-ignore
global.__DEV__ = true; global.__DEV__ = true;
class MockExternalMessaging extends ExternalMessaging { class MockExternalMessaging extends ExternalMessaging {
public mockSent: InternalMessage[] = []; public mockSent: EMMessage[] = [];
protected _sendExternal(msg: InternalMessage) { protected _sendExternal(msg: EMMessage) {
this.mockSent.push(msg); this.mockSent.push(msg);
} }
} }