Files
frontend/src/external_app/external_messaging.ts
Stefan Agner fe0fb2382a Allow to transfer all Thread datasets with TLV (#22183)
* Allow to transfer all Thread datasets with TLV

This commit allows to transfer all Thread datasets with TLV. Since
PR #22022 the preferred dataset is transmitted when using Matter
external commissioning. This commit makes the Thread configuration
dialog to have feature parity.

* Drop preferred border agent id as additional metric for default router

We always have the extended address, so use this as primary and only
metric which router is the default. The preferred border agent id gets
updated best effort.

Also use isDefaultRouter consistently in the code.
2024-10-02 15:06:06 +02:00

386 lines
8.9 KiB
TypeScript

import { AutomationConfig } from "../data/automation";
const CALLBACK_EXTERNAL_BUS = "externalBus";
interface CommandInFlight {
resolve: (data: any) => void;
reject: (err: EMError) => void;
}
export interface EMMessage {
id?: number;
type: string;
}
interface EMError {
code: string;
message: string;
}
interface EMMessageResultSuccess {
id: number;
type: "result";
success: true;
result: unknown;
}
interface EMMessageResultError {
id: number;
type: "result";
success: false;
error: EMError;
}
interface EMOutgoingMessageConfigGet extends EMMessage {
type: "config/get";
}
interface EMOutgoingMessageBarCodeScan extends EMMessage {
type: "bar_code/scan";
payload: {
title: string;
description: string;
alternative_option_label?: string;
};
}
interface EMOutgoingMessageBarCodeClose extends EMMessage {
type: "bar_code/close";
}
interface EMOutgoingMessageBarCodeNotify extends EMMessage {
type: "bar_code/notify";
payload: {
message: string;
};
}
interface EMOutgoingMessageMatterCommission extends EMMessage {
type: "matter/commission";
payload?: {
mac_extended_address: string | null;
extended_pan_id: string | null;
border_agent_id: string | null;
active_operational_dataset: string | null;
};
}
interface EMOutgoingMessageImportThreadCredentials extends EMMessage {
type: "thread/import_credentials";
}
type EMOutgoingMessageWithAnswer = {
"config/get": {
request: EMOutgoingMessageConfigGet;
response: ExternalConfig;
};
};
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
type: "exoplayer/play_hls";
payload: {
url: string;
muted: boolean;
};
}
interface EMOutgoingMessageExoplayerResize extends EMMessage {
type: "exoplayer/resize";
payload: {
left: number;
top: number;
right: number;
bottom: number;
};
}
interface EMOutgoingMessageExoplayerStop extends EMMessage {
type: "exoplayer/stop";
}
interface EMOutgoingMessageThemeUpdate extends EMMessage {
type: "theme-update";
}
interface EMOutgoingMessageHaptic extends EMMessage {
type: "haptic";
payload: { hapticType: string };
}
interface EMOutgoingMessageConnectionStatus extends EMMessage {
type: "connection-status";
payload: { event: string };
}
interface EMOutgoingMessageAppConfiguration extends EMMessage {
type: "config_screen/show";
}
interface EMOutgoingMessageTagWrite extends EMMessage {
type: "tag/write";
payload: {
name: string | null;
tag: string;
};
}
interface EMOutgoingMessageSidebarShow extends EMMessage {
type: "sidebar/show";
}
interface EMOutgoingMessageAssistShow extends EMMessage {
type: "assist/show";
payload?: {
pipeline_id: "preferred" | "last_used" | string;
start_listening: boolean;
};
}
interface EMOutgoingMessageImprovScan extends EMMessage {
type: "improv/scan";
}
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
type: "thread/store_in_platform_keychain";
payload: {
mac_extended_address: string | null;
border_agent_id: string | null;
active_operational_dataset: string;
extended_pan_id: string;
};
}
type EMOutgoingMessageWithoutAnswer =
| EMMessageResultError
| EMMessageResultSuccess
| EMOutgoingMessageAppConfiguration
| EMOutgoingMessageAssistShow
| EMOutgoingMessageBarCodeClose
| EMOutgoingMessageBarCodeNotify
| EMOutgoingMessageBarCodeScan
| EMOutgoingMessageConnectionStatus
| EMOutgoingMessageExoplayerPlayHLS
| EMOutgoingMessageExoplayerResize
| EMOutgoingMessageExoplayerStop
| EMOutgoingMessageHaptic
| EMOutgoingMessageImportThreadCredentials
| EMOutgoingMessageMatterCommission
| EMOutgoingMessageSidebarShow
| EMOutgoingMessageTagWrite
| EMOutgoingMessageThemeUpdate
| EMOutgoingMessageThreadStoreInPlatformKeychain
| EMOutgoingMessageImprovScan;
interface EMIncomingMessageRestart {
id: number;
type: "command";
command: "restart";
}
interface EMIncomingMessageShowNotifications {
id: number;
type: "command";
command: "notifications/show";
}
interface EMIncomingMessageToggleSidebar {
id: number;
type: "command";
command: "sidebar/toggle";
}
interface EMIncomingMessageShowSidebar {
id: number;
type: "command";
command: "sidebar/show";
}
interface EMIncomingMessageShowAutomationEditor {
id: number;
type: "command";
command: "automation/editor/show";
payload?: {
config?: Partial<AutomationConfig>;
};
}
export interface EMIncomingMessageBarCodeScanResult {
id: number;
type: "command";
command: "bar_code/scan_result";
payload: {
// A string decoded from the barcode data.
rawValue: string;
// https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats
format:
| "aztec"
| "code_128"
| "code_39"
| "code_93"
| "codabar"
| "data_matrix"
| "ean_13"
| "ean_8"
| "itf"
| "pdf417"
| "qr_code"
| "upc_a"
| "upc_e"
| "unknown";
};
}
export interface EMIncomingMessageBarCodeScanAborted {
id: number;
type: "command";
command: "bar_code/aborted";
payload: {
reason: "canceled" | "alternative_options";
};
}
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageShowNotifications
| EMIncomingMessageToggleSidebar
| EMIncomingMessageShowSidebar
| EMIncomingMessageShowAutomationEditor
| EMIncomingMessageBarCodeScanResult
| EMIncomingMessageBarCodeScanAborted;
type EMIncomingMessage =
| EMMessageResultSuccess
| EMMessageResultError
| EMIncomingMessageCommands;
type EMIncomingMessageHandler = (msg: EMIncomingMessageCommands) => boolean;
export interface ExternalConfig {
hasSettingsScreen: boolean;
hasSidebar: boolean;
canWriteTag: boolean;
hasExoPlayer: boolean;
canCommissionMatter: boolean;
canImportThreadCredentials: boolean;
canTransferThreadCredentialsToKeychain: boolean;
hasAssist: boolean;
hasBarCodeScanner: number;
canSetupImprov: boolean;
}
export class ExternalMessaging {
public config!: ExternalConfig;
public commands: { [msgId: number]: CommandInFlight } = {};
public msgId = 0;
private _commandHandler?: EMIncomingMessageHandler;
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<"config/get">({
type: "config/get",
});
}
public addCommandHandler(handler: EMIncomingMessageHandler) {
this._commandHandler = handler;
}
/**
* Send message to external app that expects a response.
* @param msg message to send
*/
public sendMessage<T extends keyof EMOutgoingMessageWithAnswer>(
msg: EMOutgoingMessageWithAnswer[T]["request"]
): Promise<EMOutgoingMessageWithAnswer[T]["response"]> {
const msgId = ++this.msgId;
msg.id = msgId;
this._sendExternal(msg);
return new Promise<EMOutgoingMessageWithAnswer[T]["response"]>(
(resolve, reject) => {
this.commands[msgId] = { resolve, reject };
}
);
}
/**
* Send message to external app without expecting a response.
* @param msg message to send
*/
public fireMessage(msg: EMOutgoingMessageWithoutAnswer) {
if (!msg.id) {
msg.id = ++this.msgId;
}
this._sendExternal(msg);
}
public receiveMessage(msg: EMIncomingMessage) {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Receiving message from external app", msg);
}
if (msg.type === "command") {
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(message, msg);
this.fireMessage({
id: msg.id,
type: "result",
success: false,
error: {
code,
message,
},
});
}
return;
}
const pendingCmd = this.commands[msg.id];
if (!pendingCmd) {
// eslint-disable-next-line no-console
console.warn(`Received unknown msg ID`, msg.id);
return;
}
if (msg.type === "result") {
if (msg.success) {
pendingCmd.resolve(msg.result);
} else {
pendingCmd.reject(msg.error);
}
}
}
protected _sendExternal(msg: EMMessage) {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Sending message to external app", msg);
}
if (window.externalApp) {
window.externalApp.externalBus(JSON.stringify(msg));
} else {
window.webkit!.messageHandlers.externalBus.postMessage(msg);
}
}
}