Improv external flow (#22878)

* WIP improv external flow

* Update external_messaging.ts

* use name instead, start flow

* make copy

* Update ha-config-integrations-dashboard.ts

* Update

* rename command

* Use a map instead of array for deduping
This commit is contained in:
Bram Kragten 2024-11-21 14:45:49 +01:00 committed by GitHub
parent 2899388395
commit 6b8068a22a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 9 deletions

View File

@ -13,6 +13,7 @@ import type {
EMIncomingMessageBarCodeScanAborted,
EMIncomingMessageBarCodeScanResult,
EMIncomingMessageCommands,
ImprovDiscoveredDevice,
} from "./external_messaging";
const barCodeListeners = new Set<
@ -113,6 +114,22 @@ const handleExternalMessage = (
success: true,
result: null,
});
} else if (msg.command === "improv/discovered_device") {
fireEvent(window, "improv-discovered-device", msg.payload);
bus.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else if (msg.command === "improv/device_setup_done") {
fireEvent(window, "improv-device-setup-done");
bus.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else if (msg.command === "bar_code/scan_result") {
barCodeListeners.forEach((listener) => listener(msg));
bus.fireMessage({
@ -135,3 +152,10 @@ const handleExternalMessage = (
return true;
};
declare global {
interface HASSDomEvents {
"improv-discovered-device": ImprovDiscoveredDevice;
"improv-device-setup-done": undefined;
}
}

View File

@ -134,10 +134,18 @@ interface EMOutgoingMessageAssistShow extends EMMessage {
start_listening: boolean;
};
}
interface EMOutgoingMessageImprovScan extends EMMessage {
type: "improv/scan";
}
interface EMOutgoingMessageImprovConfigureDevice extends EMMessage {
type: "improv/configure_device";
payload: {
name: string;
};
}
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
type: "thread/store_in_platform_keychain";
payload: {
@ -167,7 +175,8 @@ type EMOutgoingMessageWithoutAnswer =
| EMOutgoingMessageTagWrite
| EMOutgoingMessageThemeUpdate
| EMOutgoingMessageThreadStoreInPlatformKeychain
| EMOutgoingMessageImprovScan;
| EMOutgoingMessageImprovScan
| EMOutgoingMessageImprovConfigureDevice;
interface EMIncomingMessageRestart {
id: number;
@ -237,6 +246,23 @@ export interface EMIncomingMessageBarCodeScanAborted {
};
}
export interface ImprovDiscoveredDevice {
name: string;
}
interface EMIncomingMessageImprovDeviceDiscovered extends EMMessage {
id: number;
type: "command";
command: "improv/discovered_device";
payload: ImprovDiscoveredDevice;
}
interface EMIncomingMessageImprovDeviceSetupDone extends EMMessage {
id: number;
type: "command";
command: "improv/device_setup_done";
}
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageShowNotifications
@ -244,7 +270,9 @@ export type EMIncomingMessageCommands =
| EMIncomingMessageShowSidebar
| EMIncomingMessageShowAutomationEditor
| EMIncomingMessageBarCodeScanResult
| EMIncomingMessageBarCodeScanAborted;
| EMIncomingMessageBarCodeScanAborted
| EMIncomingMessageImprovDeviceDiscovered
| EMIncomingMessageImprovDeviceSetupDone;
type EMIncomingMessage =
| EMMessageResultSuccess

View File

@ -124,6 +124,17 @@ export class HaConfigFlowCard extends LitElement {
}
private _continueFlow() {
if (this.flow.flow_id === "external") {
this.hass.auth.external!.fireMessage({
type: "improv/configure_device",
payload: {
name:
this.flow.localized_title ||
this.flow.context.title_placeholders.name,
},
});
return;
}
showConfigFlowDialog(this, {
continueFlowId: this.flow.flow_id,
dialogClosedCallback: () => {

View File

@ -69,6 +69,7 @@ import type { HaIntegrationCard } from "./ha-integration-card";
import "./ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
import type { ImprovDiscoveredDevice } from "../../../external_app/external_messaging";
export interface ConfigEntryExtended extends Omit<ConfigEntry, "entry_id"> {
entry_id?: string;
@ -105,6 +106,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false })
public configEntriesInProgress?: DataEntryFlowProgressExtended[];
@state() private _improvDiscovered: Map<string, ImprovDiscoveredDevice> =
new Map();
@state()
private _entityRegistryEntries: EntityRegistryEntry[] = [];
@ -131,6 +135,18 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
[integration: string]: IntegrationLogInfo;
};
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener(
"improv-discovered-device",
this._handleImprovDiscovered
);
window.removeEventListener(
"improv-device-setup-done",
this._reScanImprovDevices
);
}
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
return [
subscribeEntityRegistry(this.hass.connection, (entries) => {
@ -244,8 +260,38 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
private _filterConfigEntriesInProgress = memoizeOne(
(
configEntriesInProgress: DataEntryFlowProgressExtended[],
improvDiscovered: Map<string, ImprovDiscoveredDevice>,
filter?: string
): DataEntryFlowProgressExtended[] => {
let inProgress = [...configEntriesInProgress];
const improvDiscoveredArray = Array.from(improvDiscovered.values());
if (improvDiscoveredArray.length) {
// filter out native flows that have been discovered by both mobile and local bluetooth
inProgress = inProgress.filter(
(flow) =>
!improvDiscoveredArray.some(
(discovered) => discovered.name === flow.localized_title
)
);
// add mobile flows to the list
improvDiscovered.forEach((discovered) => {
inProgress.push({
flow_id: "external",
handler: "improv_ble",
context: {
title_placeholders: {
name: discovered.name,
},
},
step_id: "bluetooth_confirm",
localized_title: discovered.name,
});
});
}
let filteredEntries: DataEntryFlowProgressExtended[];
if (filter) {
const options: IFuseOptions<DataEntryFlowProgressExtended> = {
@ -255,12 +301,12 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
threshold: 0.2,
getFn: getStripDiacriticsFn,
};
const fuse = new Fuse(configEntriesInProgress, options);
const fuse = new Fuse(inProgress, options);
filteredEntries = fuse
.search(stripDiacritics(filter))
.map((result) => result.item);
} else {
filteredEntries = configEntriesInProgress;
filteredEntries = inProgress;
}
return filteredEntries.sort((a, b) =>
caseInsensitiveStringCompare(
@ -280,6 +326,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
this._handleAdd();
}
this._scanUSBDevices();
this._scanImprovDevices();
if (isComponentLoaded(this.hass, "diagnostics")) {
fetchDiagnosticHandlers(this.hass).then((infos) => {
const handlers = {};
@ -334,6 +382,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
);
const configEntriesInProgress = this._filterConfigEntriesInProgress(
this.configEntriesInProgress,
this._improvDiscovered,
this._filter
);
@ -608,6 +657,43 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
await scanUSBDevices(this.hass);
}
private _scanImprovDevices() {
if (!this.hass.auth.external?.config.canSetupImprov) {
return;
}
window.addEventListener(
"improv-discovered-device",
this._handleImprovDiscovered
);
window.addEventListener(
"improv-device-setup-done",
this._reScanImprovDevices
);
this.hass.auth.external!.fireMessage({
type: "improv/scan",
});
}
private _reScanImprovDevices = () => {
if (!this.hass.auth.external?.config.canSetupImprov) {
return;
}
this._improvDiscovered = new Map();
this.hass.auth.external!.fireMessage({
type: "improv/scan",
});
};
private _handleImprovDiscovered = (ev) => {
this._fetchManifests(["improv_ble"]);
this._improvDiscovered.set(ev.detail.name, ev.detail);
// copy for memoize and reactive updates
this._improvDiscovered = new Map(Array.from(this._improvDiscovered));
};
private async _fetchEntitySources() {
const entitySources = await fetchEntitySourcesWithCache(this.hass);
@ -657,6 +743,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
private _handleFlowUpdated() {
getConfigFlowInProgressCollection(this.hass.connection).refresh();
this._reScanImprovDevices();
this._fetchManifests();
}
@ -664,11 +751,6 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
showAddIntegrationDialog(this, {
initialFilter: this._filter,
});
if (this.hass.auth.external?.config.canSetupImprov) {
this.hass.auth.external!.fireMessage({
type: "improv/scan",
});
}
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {