@digital-alchemy/hass 24.9.4 → 24.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/helpers/notify.helper.d.ts +2 -2
- package/package.json +16 -14
- package/scripts/mock-assistant.sh +5 -0
- package/scripts/run-e2e.sh +7 -0
- package/scripts/test.sh +2 -0
- package/src/dynamic.ts +4254 -0
- package/src/extensions/area.extension.ts +118 -0
- package/src/extensions/backup.extension.ts +63 -0
- package/src/extensions/call-proxy.extension.ts +113 -0
- package/src/extensions/config.extension.ts +119 -0
- package/src/extensions/conversation.extension.ts +46 -0
- package/src/extensions/device.extension.ts +56 -0
- package/src/extensions/entity.extension.ts +344 -0
- package/src/extensions/events.extension.ts +25 -0
- package/src/extensions/fetch-api.extension.ts +269 -0
- package/src/extensions/floor.extension.ts +76 -0
- package/src/extensions/id-by.extension.ts +157 -0
- package/src/extensions/index.ts +16 -0
- package/src/extensions/internal.extension.ts +145 -0
- package/src/extensions/label.extension.ts +83 -0
- package/src/extensions/reference.extension.ts +330 -0
- package/src/extensions/registry.extension.ts +44 -0
- package/src/extensions/websocket-api.extension.ts +554 -0
- package/src/extensions/zone.extension.ts +69 -0
- package/src/hass.module.ts +217 -0
- package/src/helpers/backup.helper.ts +11 -0
- package/src/helpers/constants.helper.ts +30 -0
- package/src/helpers/device.helper.ts +25 -0
- package/src/helpers/entity-state.helper.ts +171 -0
- package/src/helpers/features.helper.ts +580 -0
- package/src/helpers/fetch/calendar.ts +54 -0
- package/src/helpers/fetch/configuration.ts +75 -0
- package/src/helpers/fetch/index.ts +5 -0
- package/src/helpers/fetch/server-log.ts +28 -0
- package/src/helpers/fetch/service-list.ts +64 -0
- package/src/helpers/fetch/weather-forecasts.ts +86 -0
- package/src/helpers/fetch.helper.ts +328 -0
- package/src/helpers/id-by.helper.ts +53 -0
- package/src/helpers/index.ts +13 -0
- package/src/helpers/interfaces.helper.ts +340 -0
- package/src/helpers/manifest.helper.ts +0 -0
- package/src/helpers/notify.helper.ts +302 -0
- package/src/helpers/registry.ts +281 -0
- package/src/helpers/utility.helper.ts +147 -0
- package/src/helpers/websocket.helper.ts +117 -0
- package/src/index.ts +5 -0
- package/src/mock_assistant/extensions/area.extension.ts +62 -0
- package/src/mock_assistant/extensions/config.extension.ts +33 -0
- package/src/mock_assistant/extensions/device.extension.ts +44 -0
- package/src/mock_assistant/extensions/entity-registry.extension.ts +41 -0
- package/src/mock_assistant/extensions/entity.extension.ts +114 -0
- package/src/mock_assistant/extensions/events.extension.ts +37 -0
- package/src/mock_assistant/extensions/fetch.extension.ts +3 -0
- package/src/mock_assistant/extensions/fixtures.extension.ts +79 -0
- package/src/mock_assistant/extensions/floor.extension.ts +64 -0
- package/src/mock_assistant/extensions/index.ts +12 -0
- package/src/mock_assistant/extensions/label.extension.ts +64 -0
- package/src/mock_assistant/extensions/services.extension.ts +25 -0
- package/src/mock_assistant/extensions/websocket-api.extension.ts +84 -0
- package/src/mock_assistant/extensions/zone.extension.ts +65 -0
- package/src/mock_assistant/helpers/fixtures.ts +22 -0
- package/src/mock_assistant/helpers/index.ts +1 -0
- package/src/mock_assistant/index.ts +3 -0
- package/src/mock_assistant/main.ts +46 -0
- package/src/mock_assistant/mock-assistant.module.ts +90 -0
- package/src/quickboot.module.ts +23 -0
- package/src/testing/area.spec.ts +189 -0
- package/src/testing/backup.spec.ts +157 -0
- package/src/testing/config.spec.ts +188 -0
- package/src/testing/device.spec.ts +89 -0
- package/src/testing/entity.spec.ts +171 -0
- package/src/testing/events.spec.ts +78 -0
- package/src/testing/fetch-api.spec.ts +410 -0
- package/src/testing/fixtures.spec.ts +158 -0
- package/src/testing/floor.spec.ts +186 -0
- package/src/testing/id-by.spec.ts +140 -0
- package/src/testing/label.spec.ts +186 -0
- package/src/testing/ref-by.spec.ts +300 -0
- package/src/testing/websocket.spec.ts +63 -0
- package/src/testing/workflow.spec.ts +195 -0
- package/src/testing/zone.spec.ts +109 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { is, TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
TAreaId,
|
|
5
|
+
TDeviceId,
|
|
6
|
+
TFloorId,
|
|
7
|
+
TLabelId,
|
|
8
|
+
TPlatformId,
|
|
9
|
+
TUniqueId,
|
|
10
|
+
TUniqueIDMapping,
|
|
11
|
+
} from "../dynamic";
|
|
12
|
+
import {
|
|
13
|
+
ALL_DOMAINS,
|
|
14
|
+
ANY_ENTITY,
|
|
15
|
+
EntityRegistryItem,
|
|
16
|
+
IDByInterface,
|
|
17
|
+
PICK_ENTITY,
|
|
18
|
+
PICK_FROM_AREA,
|
|
19
|
+
PICK_FROM_DEVICE,
|
|
20
|
+
PICK_FROM_FLOOR,
|
|
21
|
+
PICK_FROM_LABEL,
|
|
22
|
+
PICK_FROM_PLATFORM,
|
|
23
|
+
} from "../helpers";
|
|
24
|
+
|
|
25
|
+
const check = <RAW extends ANY_ENTITY>(raw: RAW[], domains: ALL_DOMAINS[]) => {
|
|
26
|
+
if (!is.empty(domains)) {
|
|
27
|
+
raw = raw.filter(entity => is.domain(entity, domains));
|
|
28
|
+
}
|
|
29
|
+
return raw;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function IDByExtension({ hass, logger }: TServiceParams): IDByInterface {
|
|
33
|
+
// * byDomain
|
|
34
|
+
function byDomain<DOMAIN extends ALL_DOMAINS>(domain: DOMAIN) {
|
|
35
|
+
const MASTER_STATE = hass.entity._masterState();
|
|
36
|
+
return Object.keys(MASTER_STATE[domain] ?? {}).map(
|
|
37
|
+
id => `${domain}.${id}` as PICK_ENTITY<DOMAIN>,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// * unique_id
|
|
42
|
+
function unique_id<
|
|
43
|
+
UNIQUE_ID extends TUniqueId,
|
|
44
|
+
ENTITY_ID extends Extract<TUniqueIDMapping[UNIQUE_ID], ANY_ENTITY> = Extract<
|
|
45
|
+
TUniqueIDMapping[UNIQUE_ID],
|
|
46
|
+
ANY_ENTITY
|
|
47
|
+
>,
|
|
48
|
+
>(unique_id: UNIQUE_ID): ENTITY_ID {
|
|
49
|
+
hass.entity.warnEarly("byUniqueId");
|
|
50
|
+
const entity = hass.entity.registry.current.find(
|
|
51
|
+
i => i.unique_id === unique_id,
|
|
52
|
+
) as EntityRegistryItem<ENTITY_ID>;
|
|
53
|
+
if (!entity) {
|
|
54
|
+
logger.error({ name: unique_id, unique_id }, `could not find an entity`);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return entity?.entity_id;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// * label
|
|
61
|
+
function label<LABEL extends TLabelId, DOMAIN extends ALL_DOMAINS>(
|
|
62
|
+
label: LABEL,
|
|
63
|
+
...domains: DOMAIN[]
|
|
64
|
+
) {
|
|
65
|
+
hass.entity.warnEarly("label");
|
|
66
|
+
return check(
|
|
67
|
+
hass.entity.registry.current
|
|
68
|
+
.filter(i => i.labels.includes(label))
|
|
69
|
+
.map(i => i.entity_id as PICK_FROM_LABEL<LABEL, DOMAIN>),
|
|
70
|
+
domains,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// * area
|
|
75
|
+
function area<AREA extends TAreaId, DOMAIN extends ALL_DOMAINS>(
|
|
76
|
+
area: AREA,
|
|
77
|
+
...domains: DOMAIN[]
|
|
78
|
+
) {
|
|
79
|
+
hass.entity.warnEarly("area");
|
|
80
|
+
|
|
81
|
+
// find entities are associated with the area directly
|
|
82
|
+
const fromEntity = hass.entity.registry.current
|
|
83
|
+
.filter(i => i.area_id === area)
|
|
84
|
+
.map(i => i.entity_id);
|
|
85
|
+
|
|
86
|
+
// identify devices
|
|
87
|
+
const devices = new Set(
|
|
88
|
+
hass.device.current.filter(device => device.area_id === area).map(i => i.id),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// extract entities associated with device, that have not been assigned to a room
|
|
92
|
+
const fromDevice = hass.entity.registry.current
|
|
93
|
+
.filter(entity => devices.has(entity.device_id) && is.empty(entity.area_id))
|
|
94
|
+
.map(i => i.entity_id);
|
|
95
|
+
|
|
96
|
+
return check(
|
|
97
|
+
// merge lists
|
|
98
|
+
is.unique([...fromEntity, ...fromDevice]),
|
|
99
|
+
domains,
|
|
100
|
+
) as PICK_FROM_AREA<AREA, DOMAIN>[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// * device
|
|
104
|
+
function device<DEVICE extends TDeviceId, DOMAIN extends ALL_DOMAINS>(
|
|
105
|
+
device: DEVICE,
|
|
106
|
+
...domains: DOMAIN[]
|
|
107
|
+
): PICK_FROM_DEVICE<DEVICE, DOMAIN>[] {
|
|
108
|
+
hass.entity.warnEarly("device");
|
|
109
|
+
return check(
|
|
110
|
+
hass.entity.registry.current
|
|
111
|
+
.filter(i => i.device_id === device)
|
|
112
|
+
.map(i => i.entity_id as PICK_FROM_DEVICE<DEVICE, DOMAIN>),
|
|
113
|
+
domains,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// * floor
|
|
118
|
+
function floor<FLOOR extends TFloorId, DOMAIN extends ALL_DOMAINS>(
|
|
119
|
+
floor: FLOOR,
|
|
120
|
+
...domains: DOMAIN[]
|
|
121
|
+
): PICK_FROM_FLOOR<FLOOR, DOMAIN>[] {
|
|
122
|
+
hass.entity.warnEarly("floor");
|
|
123
|
+
const areas = new Set<TAreaId>(
|
|
124
|
+
hass.area.current.filter(i => i.floor_id === floor).map(i => i.area_id),
|
|
125
|
+
);
|
|
126
|
+
return check(
|
|
127
|
+
hass.entity.registry.current
|
|
128
|
+
.filter(i => areas.has(i.area_id))
|
|
129
|
+
.map(i => i.entity_id as PICK_FROM_FLOOR<FLOOR, DOMAIN>),
|
|
130
|
+
domains,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// * platform
|
|
135
|
+
function platform<PLATFORM extends TPlatformId, DOMAIN extends ALL_DOMAINS>(
|
|
136
|
+
platform: PLATFORM,
|
|
137
|
+
...domains: DOMAIN[]
|
|
138
|
+
): PICK_FROM_PLATFORM<PLATFORM, DOMAIN>[] {
|
|
139
|
+
hass.entity.warnEarly("platform");
|
|
140
|
+
return check(
|
|
141
|
+
hass.entity.registry.current
|
|
142
|
+
.filter(i => i.platform === platform)
|
|
143
|
+
.map(i => i.entity_id as PICK_FROM_PLATFORM<PLATFORM, DOMAIN>),
|
|
144
|
+
domains,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
area,
|
|
150
|
+
device,
|
|
151
|
+
domain: byDomain,
|
|
152
|
+
floor,
|
|
153
|
+
label,
|
|
154
|
+
platform,
|
|
155
|
+
unique_id,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./area.extension";
|
|
2
|
+
export * from "./backup.extension";
|
|
3
|
+
export * from "./call-proxy.extension";
|
|
4
|
+
export * from "./config.extension";
|
|
5
|
+
export * from "./device.extension";
|
|
6
|
+
export * from "./entity.extension";
|
|
7
|
+
export * from "./events.extension";
|
|
8
|
+
export * from "./fetch-api.extension";
|
|
9
|
+
export * from "./floor.extension";
|
|
10
|
+
export * from "./id-by.extension";
|
|
11
|
+
export * from "./internal.extension";
|
|
12
|
+
export * from "./label.extension";
|
|
13
|
+
export * from "./reference.extension";
|
|
14
|
+
export * from "./registry.extension";
|
|
15
|
+
export * from "./websocket-api.extension";
|
|
16
|
+
export * from "./zone.extension";
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { FIRST, InternalError, is, TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
import { createWriteStream } from "fs";
|
|
3
|
+
import { pipeline } from "stream";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
buildFilterString,
|
|
8
|
+
DownloadOptions,
|
|
9
|
+
FetchArguments,
|
|
10
|
+
FetcherOptions,
|
|
11
|
+
FetchProcessTypes,
|
|
12
|
+
FetchWith,
|
|
13
|
+
MaybeHttpError,
|
|
14
|
+
TFetchBody,
|
|
15
|
+
} from "../helpers";
|
|
16
|
+
|
|
17
|
+
const streamPipeline = promisify(pipeline);
|
|
18
|
+
|
|
19
|
+
export function FetchInternals({ logger, context: parentContext }: TServiceParams) {
|
|
20
|
+
return ({ headers: base_headers, baseUrl: base_url, context: logContext }: FetcherOptions) => {
|
|
21
|
+
const capabilities: string[] = [];
|
|
22
|
+
|
|
23
|
+
if (!is.empty(capabilities)) {
|
|
24
|
+
logger.trace({ capabilities, name: logContext }, `initialized fetcher`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function checkForHttpErrors<T extends unknown = unknown>(maybeError: MaybeHttpError): T {
|
|
28
|
+
if (
|
|
29
|
+
is.object(maybeError) &&
|
|
30
|
+
maybeError !== null &&
|
|
31
|
+
is.number(maybeError.statusCode) &&
|
|
32
|
+
is.string(maybeError.error)
|
|
33
|
+
) {
|
|
34
|
+
// Log the error if needed
|
|
35
|
+
logger.error({ error: maybeError, name: logContext }, maybeError.message);
|
|
36
|
+
|
|
37
|
+
// Throw a FetchRequestError
|
|
38
|
+
// throw new FetchRequestError(maybeError);
|
|
39
|
+
throw new InternalError(logContext || parentContext, maybeError.error, maybeError.message);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return maybeError as T;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// #MARK: fetchHandleResponse
|
|
46
|
+
async function fetchHandleResponse<T extends unknown = unknown>(
|
|
47
|
+
process: FetchProcessTypes,
|
|
48
|
+
response: Response,
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
if (process === false || process === "raw") {
|
|
51
|
+
return response as T;
|
|
52
|
+
}
|
|
53
|
+
const text = await response.text();
|
|
54
|
+
if (process === "text") {
|
|
55
|
+
return text as unknown as T;
|
|
56
|
+
}
|
|
57
|
+
if (!["{", "["].includes(text.charAt(FIRST))) {
|
|
58
|
+
if (["OK"].includes(text)) {
|
|
59
|
+
logger.debug({ name: logContext, text }, "full response text");
|
|
60
|
+
} else {
|
|
61
|
+
// It's probably a coding error error, and not something a user did.
|
|
62
|
+
// Will try to keep the array up to date if any other edge cases pop up
|
|
63
|
+
logger.warn({ name: logContext, text }, `unexpected api Response`);
|
|
64
|
+
}
|
|
65
|
+
return text as T;
|
|
66
|
+
}
|
|
67
|
+
const parsed = JSON.parse(text);
|
|
68
|
+
return checkForHttpErrors<T>(parsed);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fetchCreateUrl({ rawUrl, url, ...fetchWith }: FetchWith): string {
|
|
72
|
+
let out = url || "";
|
|
73
|
+
if (!rawUrl) {
|
|
74
|
+
const base = fetchWith.baseUrl || fetchWrapper.base_url;
|
|
75
|
+
out = base + url;
|
|
76
|
+
}
|
|
77
|
+
if (!is.empty(fetchWith.params)) {
|
|
78
|
+
out = `${out}?${buildFilterString(fetchWith)}`;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// #MARK: execFetch
|
|
84
|
+
async function exec<T, BODY extends TFetchBody = undefined>({
|
|
85
|
+
body,
|
|
86
|
+
headers = {},
|
|
87
|
+
method = "get",
|
|
88
|
+
process,
|
|
89
|
+
...fetchWith
|
|
90
|
+
}: Partial<FetchArguments<BODY>>) {
|
|
91
|
+
const contentType = is.object(body) ? { "Content-Type": "application/json" } : {};
|
|
92
|
+
const result = await global.fetch(fetchCreateUrl(fetchWith), {
|
|
93
|
+
body: is.object(body) ? JSON.stringify(body) : body,
|
|
94
|
+
headers: {
|
|
95
|
+
...contentType,
|
|
96
|
+
...fetchWrapper.base_headers,
|
|
97
|
+
...headers,
|
|
98
|
+
},
|
|
99
|
+
method,
|
|
100
|
+
});
|
|
101
|
+
return await fetchHandleResponse<T>(process, result);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function download({
|
|
105
|
+
destination,
|
|
106
|
+
body,
|
|
107
|
+
headers = {},
|
|
108
|
+
method = "get",
|
|
109
|
+
...fetchWith
|
|
110
|
+
}: DownloadOptions) {
|
|
111
|
+
const url: string = await fetchCreateUrl(fetchWith);
|
|
112
|
+
const response = await fetch(url, {
|
|
113
|
+
body: is.object(body) ? JSON.stringify(body) : body,
|
|
114
|
+
headers: { ...fetchWrapper.base_headers, ...headers },
|
|
115
|
+
method,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const stream = createWriteStream(destination);
|
|
119
|
+
await streamPipeline(response.body, stream);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// #MARK: return object
|
|
123
|
+
const fetchWrapper = {
|
|
124
|
+
base_headers,
|
|
125
|
+
base_url,
|
|
126
|
+
download,
|
|
127
|
+
exec,
|
|
128
|
+
/**
|
|
129
|
+
* @deprecated set base_url directly
|
|
130
|
+
*/
|
|
131
|
+
setBaseUrl: (url: string) => (fetchWrapper.base_url = url),
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated set base_headers directly
|
|
134
|
+
*/
|
|
135
|
+
setHeaders: (headers: Record<string, string>) => (fetchWrapper.base_headers = headers),
|
|
136
|
+
};
|
|
137
|
+
return fetchWrapper;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type TFetch = <T, BODY extends object = object>(
|
|
142
|
+
fetchWith: Partial<FetchArguments<BODY>>,
|
|
143
|
+
) => Promise<T>;
|
|
144
|
+
|
|
145
|
+
export type TDownload = (fetchWith: DownloadOptions) => Promise<void>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { debounce, TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
|
|
3
|
+
import { TLabelId } from "../dynamic";
|
|
4
|
+
import {
|
|
5
|
+
EARLY_ON_READY,
|
|
6
|
+
HassLabelService,
|
|
7
|
+
LABEL_REGISTRY_UPDATED,
|
|
8
|
+
LabelDefinition,
|
|
9
|
+
LabelOptions,
|
|
10
|
+
} from "../helpers";
|
|
11
|
+
|
|
12
|
+
export function Label({
|
|
13
|
+
hass,
|
|
14
|
+
config,
|
|
15
|
+
logger,
|
|
16
|
+
lifecycle,
|
|
17
|
+
event,
|
|
18
|
+
context,
|
|
19
|
+
}: TServiceParams): HassLabelService {
|
|
20
|
+
hass.socket.onConnect(async () => {
|
|
21
|
+
let loading = new Promise<void>(async done => {
|
|
22
|
+
hass.label.current = await hass.label.list();
|
|
23
|
+
loading = undefined;
|
|
24
|
+
done();
|
|
25
|
+
});
|
|
26
|
+
lifecycle.onReady(async () => loading && (await loading), EARLY_ON_READY);
|
|
27
|
+
|
|
28
|
+
hass.socket.subscribe({
|
|
29
|
+
context,
|
|
30
|
+
event_type: "label_registry_updated",
|
|
31
|
+
async exec() {
|
|
32
|
+
await debounce(LABEL_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
|
|
33
|
+
hass.label.current = await hass.label.list();
|
|
34
|
+
logger.debug(`label registry updated`);
|
|
35
|
+
event.emit(LABEL_REGISTRY_UPDATED);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function create(details: LabelOptions) {
|
|
41
|
+
return await new Promise<void>(async done => {
|
|
42
|
+
event.once(LABEL_REGISTRY_UPDATED, done);
|
|
43
|
+
await hass.socket.sendMessage({
|
|
44
|
+
type: "config/label_registry/create",
|
|
45
|
+
...details,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function deleteLabel(label_id: TLabelId) {
|
|
51
|
+
return await new Promise<void>(async done => {
|
|
52
|
+
event.once(LABEL_REGISTRY_UPDATED, done);
|
|
53
|
+
await hass.socket.sendMessage({
|
|
54
|
+
label_id,
|
|
55
|
+
type: "config/label_registry/delete",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function list() {
|
|
61
|
+
return await hass.socket.sendMessage<LabelDefinition[]>({
|
|
62
|
+
type: "config/label_registry/list",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function update(details: LabelDefinition) {
|
|
67
|
+
return await new Promise<void>(async done => {
|
|
68
|
+
event.once(LABEL_REGISTRY_UPDATED, done);
|
|
69
|
+
await hass.socket.sendMessage({
|
|
70
|
+
type: "config/label_registry/update",
|
|
71
|
+
...details,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
create,
|
|
78
|
+
current: [] as LabelDefinition[],
|
|
79
|
+
delete: deleteLabel,
|
|
80
|
+
list,
|
|
81
|
+
update,
|
|
82
|
+
};
|
|
83
|
+
}
|