Compare commits

...

1 Commits

Author SHA1 Message Date
Bram Kragten 0725ce2053 Use iOS provided device name for matter, and wait for iOS flow to be done 2026-06-04 16:22:13 +02:00
4 changed files with 101 additions and 3 deletions
@@ -5,6 +5,7 @@ in core bundle slows things down and causes duplicate registration.
This is the entry point for providing external app stuff from app entrypoint.
*/
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
import { navigate } from "../common/navigate";
@@ -15,6 +16,7 @@ import type {
EMIncomingMessageBarCodeScanResult,
EMIncomingMessageCommands,
ImprovDiscoveredDevice,
MatterCommissionFinish,
} from "./external_messaging";
const barCodeListeners = new Set<
@@ -91,6 +93,8 @@ export const handleExternalMessage = (
fireEvent(window, "improv-discovered-device", msg.payload);
} else if (msg.command === "improv/device_setup_done") {
fireEvent(window, "improv-device-setup-done");
} else if (msg.command === "matter/commission/finish") {
fireEvent(window, "matter-commission-finish", msg.payload);
} else if (msg.command === "bar_code/scan_result") {
barCodeListeners.forEach((listener) => listener(msg));
} else if (msg.command === "bar_code/aborted") {
@@ -115,5 +119,10 @@ declare global {
interface HASSDomEvents {
"improv-discovered-device": ImprovDiscoveredDevice;
"improv-device-setup-done": undefined;
"matter-commission-finish": MatterCommissionFinish;
}
interface GlobalEventHandlersEventMap {
"matter-commission-finish": HASSDomEvent<MatterCommissionFinish>;
}
}
+14
View File
@@ -320,6 +320,18 @@ export interface EMIncomingMessageKioskModeSet {
};
}
export interface MatterCommissionFinish {
name: string | null;
success: boolean;
}
export interface EMIncomingMessageMatterCommissionFinish extends EMMessage {
id: number;
type: "command";
command: "matter/commission/finish";
payload: MatterCommissionFinish;
}
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageNavigate
@@ -331,6 +343,7 @@ export type EMIncomingMessageCommands =
| EMIncomingMessageBarCodeScanAborted
| EMIncomingMessageImprovDeviceDiscovered
| EMIncomingMessageImprovDeviceSetupDone
| EMIncomingMessageMatterCommissionFinish
| EMIncomingMessageKioskModeSet;
type EMIncomingMessage =
@@ -346,6 +359,7 @@ export interface ExternalConfig {
canWriteTag?: boolean;
hasExoPlayer?: boolean;
canCommissionMatter?: boolean;
hasMatterStatusReport?: boolean;
canImportThreadCredentials?: boolean;
canTransferThreadCredentialsToKeychain?: boolean;
hasAssist?: boolean;
@@ -2,6 +2,7 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDomain } from "../../../../../common/entity/compute_domain";
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
@@ -10,6 +11,7 @@ import "../../../../../components/ha-dialog-footer";
import "../../../../../components/ha-icon-button-arrow-prev";
import "../../../../../components/ha-button";
import "../../../../../components/ha-dialog";
import type { MatterCommissionFinish } from "../../../../../external_app/external_messaging";
import {
commissionMatterDevice,
watchForNewMatterDevice,
@@ -82,6 +84,10 @@ class DialogMatterAddDevice extends LitElement {
@state() private _mainEntity?: ExtEntityRegistryEntry;
@state() private _proposedDeviceName?: string;
@state() private _commissioningFinished = false;
@state() private _deviceAddedState: {
name: string;
area: string | undefined;
@@ -104,15 +110,63 @@ class DialogMatterAddDevice extends LitElement {
// make sure a refresh of the page will navigate to the device page, old iOS apps will refresh the webview when commissioning is done
setRefreshUrl(`/config/devices/device/${device.id}`);
this._newDevice = device;
this._step = "device_added";
this._fetchMainEntity();
this._maybeShowDeviceAdded();
});
if (this._waitForCommissioningFinish) {
window.addEventListener(
"matter-commission-finish",
this._handleCommissionFinish
);
}
}
public closeDialog(): void {
this._open = false;
}
private get _waitForCommissioningFinish(): boolean {
// When the app supports reporting Matter commissioning status, defer
// advancing past the spinner until we receive matter/commission/finish.
return !!this.hass.auth.external?.config.hasMatterStatusReport;
}
private _maybeShowDeviceAdded(): void {
if (!this._newDevice) {
return;
}
if (this._waitForCommissioningFinish && !this._commissioningFinished) {
return;
}
this._step = "device_added";
}
private _handleCommissionFinish = (
ev: HASSDomEvent<MatterCommissionFinish>
) => {
const { name, success } = ev.detail;
if (!success) {
if (this._newDevice) {
// Device already showed up in the registry — ignore the failure signal
// and let the user finish the rename flow.
return;
}
showToast(this, {
message: this.hass.localize(
"ui.dialogs.matter-add-device.add_device_failed"
),
duration: 2000,
});
this.closeDialog();
return;
}
this._commissioningFinished = true;
if (name) {
this._proposedDeviceName = name;
}
this._maybeShowDeviceAdded();
};
protected updated(changedProps: Map<string, unknown>): void {
// Retry fetching main entity when hass updates (entities may not be available immediately)
if (
@@ -160,6 +214,8 @@ class DialogMatterAddDevice extends LitElement {
this._newDevice = undefined;
this._mainEntity = undefined;
this._mainEntityFetched = false;
this._proposedDeviceName = undefined;
this._commissioningFinished = false;
this._deviceAddedState = {
name: "",
area: undefined,
@@ -168,6 +224,10 @@ class DialogMatterAddDevice extends LitElement {
};
this._unsub?.();
this._unsub = undefined;
window.removeEventListener(
"matter-commission-finish",
this._handleCommissionFinish
);
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -211,6 +271,7 @@ class DialogMatterAddDevice extends LitElement {
hass: this.hass,
device: this._newDevice,
mainEntity: this._mainEntity,
proposedName: this._proposedDeviceName,
}
)}
</div>
@@ -36,6 +36,8 @@ class MatterAddDeviceDeviceAdded extends LitElement {
@property({ attribute: false }) public mainEntity?: ExtEntityRegistryEntry;
@property({ attribute: false }) public proposedName?: string;
@state() private _deviceName = "";
@state() private _area: string | undefined;
@@ -49,8 +51,18 @@ class MatterAddDeviceDeviceAdded extends LitElement {
protected willUpdate(changedProps: PropertyValues) {
if (!this._initialized && this.device) {
this._initialized = true;
this._deviceName = computeDeviceName(this.device) ?? "";
this._deviceName =
this.proposedName || (computeDeviceName(this.device) ?? "");
this._area = this.device.area_id ?? undefined;
} else if (
changedProps.has("proposedName") &&
this.proposedName &&
this.device &&
this._deviceName === (computeDeviceName(this.device) ?? "")
) {
// proposedName arrived after we initialized, and the user hasn't
// changed the name yet — adopt it
this._deviceName = this.proposedName;
}
if (
!this._deviceClassInitialized &&
@@ -158,7 +170,9 @@ class MatterAddDeviceDeviceAdded extends LitElement {
referrerpolicy="no-referrer"
/>
<div class="device-name">
<span>${computeDeviceName(this.device)}</span>
<span
>${this.proposedName || computeDeviceName(this.device)}</span
>
<span class="secondary">Matter</span>
</div>
</div>