374 lines
8.5 KiB
TypeScript
374 lines
8.5 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";
|
|
}
|
|
|
|
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 EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
|
type: "thread/store_in_platform_keychain";
|
|
payload: {
|
|
mac_extended_address: string;
|
|
border_agent_id: string | null;
|
|
active_operational_dataset: string;
|
|
};
|
|
}
|
|
|
|
type EMOutgoingMessageWithoutAnswer =
|
|
| EMMessageResultError
|
|
| EMMessageResultSuccess
|
|
| EMOutgoingMessageAppConfiguration
|
|
| EMOutgoingMessageAssistShow
|
|
| EMOutgoingMessageBarCodeClose
|
|
| EMOutgoingMessageBarCodeNotify
|
|
| EMOutgoingMessageBarCodeScan
|
|
| EMOutgoingMessageConnectionStatus
|
|
| EMOutgoingMessageExoplayerPlayHLS
|
|
| EMOutgoingMessageExoplayerResize
|
|
| EMOutgoingMessageExoplayerStop
|
|
| EMOutgoingMessageHaptic
|
|
| EMOutgoingMessageImportThreadCredentials
|
|
| EMOutgoingMessageMatterCommission
|
|
| EMOutgoingMessageSidebarShow
|
|
| EMOutgoingMessageTagWrite
|
|
| EMOutgoingMessageThemeUpdate
|
|
| EMOutgoingMessageThreadStoreInPlatformKeychain;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|