@digital-alchemy/hass 0.2.0
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/LICENSE +21 -0
- package/dist/dynamic.d.ts +1126 -0
- package/dist/dynamic.js +153 -0
- package/dist/dynamic.js.map +1 -0
- package/dist/extensions/call-proxy.extension.d.ts +4 -0
- package/dist/extensions/call-proxy.extension.js +88 -0
- package/dist/extensions/call-proxy.extension.js.map +1 -0
- package/dist/extensions/config.extension.d.ts +2 -0
- package/dist/extensions/config.extension.js +53 -0
- package/dist/extensions/config.extension.js.map +1 -0
- package/dist/extensions/entity-manager.extension.d.ts +61 -0
- package/dist/extensions/entity-manager.extension.js +212 -0
- package/dist/extensions/entity-manager.extension.js.map +1 -0
- package/dist/extensions/fetch-api.extension.d.ts +29 -0
- package/dist/extensions/fetch-api.extension.js +174 -0
- package/dist/extensions/fetch-api.extension.js.map +1 -0
- package/dist/extensions/index.d.ts +6 -0
- package/dist/extensions/index.js +10 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/utilities.extension.d.ts +9 -0
- package/dist/extensions/utilities.extension.js +43 -0
- package/dist/extensions/utilities.extension.js.map +1 -0
- package/dist/extensions/websocket-api.extension.d.ts +39 -0
- package/dist/extensions/websocket-api.extension.js +363 -0
- package/dist/extensions/websocket-api.extension.js.map +1 -0
- package/dist/hass.module.d.ts +80 -0
- package/dist/hass.module.js +83 -0
- package/dist/hass.module.js.map +1 -0
- package/dist/helpers/backup.helper.d.ts +11 -0
- package/dist/helpers/backup.helper.js +3 -0
- package/dist/helpers/backup.helper.js.map +1 -0
- package/dist/helpers/constants.helper.d.ts +54 -0
- package/dist/helpers/constants.helper.js +63 -0
- package/dist/helpers/constants.helper.js.map +1 -0
- package/dist/helpers/entity-state.helper.d.ts +45 -0
- package/dist/helpers/entity-state.helper.js +9 -0
- package/dist/helpers/entity-state.helper.js.map +1 -0
- package/dist/helpers/fetch/calendar.d.ts +54 -0
- package/dist/helpers/fetch/calendar.js +3 -0
- package/dist/helpers/fetch/calendar.js.map +1 -0
- package/dist/helpers/fetch/configuration.d.ts +34 -0
- package/dist/helpers/fetch/configuration.js +3 -0
- package/dist/helpers/fetch/configuration.js.map +1 -0
- package/dist/helpers/fetch/index.d.ts +4 -0
- package/dist/helpers/fetch/index.js +8 -0
- package/dist/helpers/fetch/index.js.map +1 -0
- package/dist/helpers/fetch/server-log.d.ts +10 -0
- package/dist/helpers/fetch/server-log.js +20 -0
- package/dist/helpers/fetch/server-log.js.map +1 -0
- package/dist/helpers/fetch/service-list.d.ts +51 -0
- package/dist/helpers/fetch/service-list.js +3 -0
- package/dist/helpers/fetch/service-list.js.map +1 -0
- package/dist/helpers/index.d.ts +7 -0
- package/dist/helpers/index.js +11 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/metrics.helper.d.ts +13 -0
- package/dist/helpers/metrics.helper.js +30 -0
- package/dist/helpers/metrics.helper.js.map +1 -0
- package/dist/helpers/utility.helper.d.ts +53 -0
- package/dist/helpers/utility.helper.js +30 -0
- package/dist/helpers/utility.helper.js.map +1 -0
- package/dist/helpers/websocket.helper.d.ts +129 -0
- package/dist/helpers/websocket.helper.js +3 -0
- package/dist/helpers/websocket.helper.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FetchAPI = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@digital-alchemy/core");
|
|
6
|
+
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
|
7
|
+
const __1 = require("..");
|
|
8
|
+
function FetchAPI({ logger, lifecycle, context, config, }) {
|
|
9
|
+
let fetcher;
|
|
10
|
+
let downloader;
|
|
11
|
+
// Load configurations
|
|
12
|
+
lifecycle.onPostConfig(() => {
|
|
13
|
+
const fetch = core_1.ZCC.createFetcher({
|
|
14
|
+
baseUrl: config.hass.BASE_URL,
|
|
15
|
+
context,
|
|
16
|
+
headers: { Authorization: `Bearer ${config.hass.TOKEN}` },
|
|
17
|
+
});
|
|
18
|
+
fetcher = fetch.fetch;
|
|
19
|
+
downloader = fetch.download;
|
|
20
|
+
}, __1.PostConfigPriorities.FETCH);
|
|
21
|
+
async function calendarSearch({ calendar, start = (0, dayjs_1.default)(), end, }) {
|
|
22
|
+
if (Array.isArray(calendar)) {
|
|
23
|
+
const list = await Promise.all(calendar.map(async (cal) => await calendarSearch({ calendar: cal, end, start })));
|
|
24
|
+
return list
|
|
25
|
+
.flat()
|
|
26
|
+
.sort((a, b) => a.start.isSame(b.start)
|
|
27
|
+
? core_1.NO_CHANGE
|
|
28
|
+
: a.start.isAfter(b.start)
|
|
29
|
+
? core_1.UP
|
|
30
|
+
: core_1.DOWN);
|
|
31
|
+
}
|
|
32
|
+
const params = { end: end.toISOString(), start: start.toISOString() };
|
|
33
|
+
const events = await fetcher({
|
|
34
|
+
params,
|
|
35
|
+
url: `/api/calendars/${calendar}`,
|
|
36
|
+
});
|
|
37
|
+
logger.trace({ ...params }, `%s search found %s events`, calendar, events.length);
|
|
38
|
+
return events.map(({ start, end, ...extra }) => ({
|
|
39
|
+
...extra,
|
|
40
|
+
end: (0, dayjs_1.default)(end.dateTime),
|
|
41
|
+
start: (0, dayjs_1.default)(start.dateTime),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
async function callService(serviceName, data) {
|
|
45
|
+
const [domain, service] = serviceName.split(".");
|
|
46
|
+
return await fetcher({
|
|
47
|
+
body: data,
|
|
48
|
+
method: "post",
|
|
49
|
+
url: `/api/services/${domain}/${service}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function checkConfig() {
|
|
53
|
+
logger.trace(`check config`);
|
|
54
|
+
return await fetcher({
|
|
55
|
+
method: `post`,
|
|
56
|
+
url: `/api/config/core/check_config`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function download(destination, fetchWith) {
|
|
60
|
+
await downloader({
|
|
61
|
+
...fetchWith,
|
|
62
|
+
baseUrl: config.hass.BASE_URL,
|
|
63
|
+
destination,
|
|
64
|
+
headers: { Authorization: `Bearer ${config.hass.TOKEN}` },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function fetchEntityCustomizations(entityId) {
|
|
68
|
+
return await fetcher({
|
|
69
|
+
url: `/api/config/customize/config/${entityId}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function fetchEntityHistory(entity_id, from, to, extra = {}) {
|
|
73
|
+
logger.info({ entity_id, from: from.toISOString(), to: to.toISOString() }, `fetch entity history`);
|
|
74
|
+
const result = await fetcher({
|
|
75
|
+
params: {
|
|
76
|
+
end_time: to.toISOString(),
|
|
77
|
+
filter_entity_id: entity_id,
|
|
78
|
+
...extra,
|
|
79
|
+
},
|
|
80
|
+
url: `/api/history/period/${from.toISOString()}`,
|
|
81
|
+
});
|
|
82
|
+
if (!Array.isArray(result)) {
|
|
83
|
+
logger.error({ result }, `unexpected return result`);
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const [out] = result;
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
async function fireEvent(event, data) {
|
|
90
|
+
logger.trace({ name: event, ...data }, `Firing event`);
|
|
91
|
+
const response = await fetcher({
|
|
92
|
+
// body: data,
|
|
93
|
+
body: {},
|
|
94
|
+
method: "post",
|
|
95
|
+
url: `/api/events/${event}`,
|
|
96
|
+
});
|
|
97
|
+
if (response?.message !== `Event ${event} fired.`) {
|
|
98
|
+
logger.debug({ response }, `unexpected response from firing event`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function getAllEntities() {
|
|
102
|
+
logger.trace(`get all entities`);
|
|
103
|
+
return await fetcher({ url: `/api/states` });
|
|
104
|
+
}
|
|
105
|
+
async function getHassConfig() {
|
|
106
|
+
logger.trace(`get config`);
|
|
107
|
+
return await fetcher({ url: `/api/config` });
|
|
108
|
+
}
|
|
109
|
+
async function getLogs() {
|
|
110
|
+
logger.trace(`get logs`);
|
|
111
|
+
const results = await fetcher({
|
|
112
|
+
url: `/api/error/all`,
|
|
113
|
+
});
|
|
114
|
+
return results.map(i => {
|
|
115
|
+
i.timestamp = Math.floor(i.timestamp * core_1.SECOND);
|
|
116
|
+
i.first_occurred = Math.floor(i.first_occurred * core_1.SECOND);
|
|
117
|
+
return i;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function getRawLogs() {
|
|
121
|
+
logger.trace(`get raw logs`);
|
|
122
|
+
return await fetcher({ process: "text", url: `/api/error_log` });
|
|
123
|
+
}
|
|
124
|
+
async function listServices() {
|
|
125
|
+
logger.trace(`list services`);
|
|
126
|
+
return await fetcher({ url: `/api/services` });
|
|
127
|
+
}
|
|
128
|
+
async function updateEntity(entity_id, { attributes, state }) {
|
|
129
|
+
const body = {};
|
|
130
|
+
if (state !== undefined) {
|
|
131
|
+
body.state = state;
|
|
132
|
+
}
|
|
133
|
+
if (!core_1.is.empty(attributes)) {
|
|
134
|
+
body.attributes = attributes;
|
|
135
|
+
}
|
|
136
|
+
logger.trace({ ...body, name: entity_id }, `set entity state`);
|
|
137
|
+
await fetcher({ body, method: "post", url: `/api/states/${entity_id}` });
|
|
138
|
+
}
|
|
139
|
+
async function webhook(name, data = {}) {
|
|
140
|
+
logger.trace({ ...data, name }, `webhook`);
|
|
141
|
+
await fetcher({
|
|
142
|
+
body: data,
|
|
143
|
+
method: "post",
|
|
144
|
+
process: "text",
|
|
145
|
+
url: `/api/webhook/${name}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function checkCredentials() {
|
|
149
|
+
logger.trace(`check credentials`);
|
|
150
|
+
return await fetcher({
|
|
151
|
+
url: `/api/`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
calendarSearch,
|
|
156
|
+
callService,
|
|
157
|
+
checkConfig,
|
|
158
|
+
checkCredentials,
|
|
159
|
+
download,
|
|
160
|
+
fetch: fetcher,
|
|
161
|
+
fetchEntityCustomizations,
|
|
162
|
+
fetchEntityHistory,
|
|
163
|
+
fireEvent,
|
|
164
|
+
getAllEntities,
|
|
165
|
+
getConfig: getHassConfig,
|
|
166
|
+
getLogs,
|
|
167
|
+
getRawLogs,
|
|
168
|
+
listServices,
|
|
169
|
+
updateEntity,
|
|
170
|
+
webhook,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
exports.FetchAPI = FetchAPI;
|
|
174
|
+
//# sourceMappingURL=fetch-api.extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-api.extension.js","sourceRoot":"","sources":["../../src/extensions/fetch-api.extension.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./call-proxy.extension"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./config.extension"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./entity-manager.extension"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./fetch-api.extension"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./utilities.extension"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./websocket-api.extension"), exports);
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":";;;AAAA,iEAAuC;AACvC,6DAAmC;AACnC,qEAA2C;AAC3C,gEAAsC;AACtC,gEAAsC;AACtC,oEAA0C"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
import { BackupResponse, HomeAssistantBackup } from "..";
|
|
3
|
+
export declare function Utilities({ logger, hass }: TServiceParams): {
|
|
4
|
+
backup: {
|
|
5
|
+
generate: () => Promise<HomeAssistantBackup>;
|
|
6
|
+
list: () => Promise<BackupResponse>;
|
|
7
|
+
remove: (slug: string) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Utilities = void 0;
|
|
4
|
+
const core_1 = require("@digital-alchemy/core");
|
|
5
|
+
const __1 = require("..");
|
|
6
|
+
function Utilities({ logger, hass }) {
|
|
7
|
+
async function generate() {
|
|
8
|
+
let current = await list();
|
|
9
|
+
// const originalLength = current.backups.length;
|
|
10
|
+
if (current.backing_up) {
|
|
11
|
+
logger.warn(`a backup is currently in progress. Waiting for that to complete instead.`);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
logger.info("initiating new backup");
|
|
15
|
+
hass.socket.sendMessage({
|
|
16
|
+
type: __1.HASSIO_WS_COMMAND.generate_backup,
|
|
17
|
+
});
|
|
18
|
+
while (current.backing_up === false) {
|
|
19
|
+
logger.debug("... waiting");
|
|
20
|
+
await (0, core_1.sleep)(core_1.HALF * core_1.SECOND);
|
|
21
|
+
current = await list();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
while (current.backing_up === true) {
|
|
25
|
+
logger.debug("... waiting");
|
|
26
|
+
await (0, core_1.sleep)(core_1.HALF * core_1.SECOND);
|
|
27
|
+
current = await list();
|
|
28
|
+
}
|
|
29
|
+
logger.info(`backup complete`);
|
|
30
|
+
return current.backups.pop();
|
|
31
|
+
}
|
|
32
|
+
async function list() {
|
|
33
|
+
return await hass.socket.sendMessage({
|
|
34
|
+
type: __1.HASSIO_WS_COMMAND.backup_info,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function remove(slug) {
|
|
38
|
+
await hass.socket.sendMessage({ slug, type: __1.HASSIO_WS_COMMAND.remove_backup }, false);
|
|
39
|
+
}
|
|
40
|
+
return { backup: { generate, list, remove } };
|
|
41
|
+
}
|
|
42
|
+
exports.Utilities = Utilities;
|
|
43
|
+
//# sourceMappingURL=utilities.extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utilities.extension.js","sourceRoot":"","sources":["../../src/extensions/utilities.extension.ts"],"names":[],"mappings":";;;AAAA,gDAA4E;AAE5E,0BAA4E;AAE5E,SAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAkB;IACxD,KAAK,UAAU,QAAQ;QACrB,IAAI,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;QAC3B,iDAAiD;QACjD,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CACT,0EAA0E,CAC3E,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;gBACtB,IAAI,EAAE,qBAAiB,CAAC,eAAe;aACxC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;gBACpC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBAC5B,MAAM,IAAA,YAAK,EAAC,WAAI,GAAG,aAAM,CAAC,CAAC;gBAC3B,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YACnC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YAC5B,MAAM,IAAA,YAAK,EAAC,WAAI,GAAG,aAAM,CAAC,CAAC;YAC3B,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;QACzB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAiB;YACnD,IAAI,EAAE,qBAAiB,CAAC,WAAW;SACpC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,MAAM,CAAC,IAAY;QAChC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAC3B,EAAE,IAAI,EAAE,IAAI,EAAE,qBAAiB,CAAC,aAAa,EAAE,EAC/C,KAAK,CACN,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;AAChD,CAAC;AA1CD,8BA0CC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TBlackHole, TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
import { HASSIO_WS_COMMAND, OnHassEventOptions } from "..";
|
|
3
|
+
export declare const SOCKET_CONNECTED = "SOCKET_CONNECTED";
|
|
4
|
+
export declare function WebsocketAPI({ context, event, hass, config, lifecycle, logger, scheduler, }: TServiceParams): {
|
|
5
|
+
/**
|
|
6
|
+
* Convenient wrapper for sendMessage
|
|
7
|
+
*/
|
|
8
|
+
fireEvent: (event_type: string, event_data?: object) => Promise<unknown>;
|
|
9
|
+
getConnectionActive: () => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Set up a new websocket connection to home assistant
|
|
12
|
+
*
|
|
13
|
+
* This doesn't normally need to be called by applications, the extension self manages
|
|
14
|
+
*/
|
|
15
|
+
init: () => Promise<void>;
|
|
16
|
+
onConnect: (callback: () => TBlackHole) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Attach to the incoming stream of socket events. Do your own filtering and processing from there
|
|
19
|
+
*
|
|
20
|
+
* Returns removal function
|
|
21
|
+
*/
|
|
22
|
+
onEvent: <DATA extends object>({ context, label, event, once, exec, }: OnHassEventOptions<DATA>) => () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Send a message to home assistant via the socket connection
|
|
25
|
+
*
|
|
26
|
+
* Applications probably want a higher level function than this
|
|
27
|
+
*/
|
|
28
|
+
sendMessage: <RESPONSE_VALUE extends unknown = unknown>(data: {
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
type: `${HASSIO_WS_COMMAND}`;
|
|
31
|
+
id?: number;
|
|
32
|
+
}, waitForResponse?: boolean, subscription?: () => void) => Promise<RESPONSE_VALUE>;
|
|
33
|
+
/**
|
|
34
|
+
* remove the current socket connection to home assistant
|
|
35
|
+
*
|
|
36
|
+
* will need to call init() again to start up
|
|
37
|
+
*/
|
|
38
|
+
teardown: () => Promise<void>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebsocketAPI = exports.SOCKET_CONNECTED = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@digital-alchemy/core");
|
|
6
|
+
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
|
7
|
+
const events_1 = tslib_1.__importDefault(require("events"));
|
|
8
|
+
const process_1 = require("process");
|
|
9
|
+
const ws_1 = tslib_1.__importDefault(require("ws"));
|
|
10
|
+
const __1 = require("..");
|
|
11
|
+
let connection;
|
|
12
|
+
const CONNECTION_OPEN = 1;
|
|
13
|
+
let CONNECTION_ACTIVE = false;
|
|
14
|
+
const CLEANUP_INTERVAL = 5;
|
|
15
|
+
const PING_INTERVAL = 10;
|
|
16
|
+
const UNLIMITED = 0;
|
|
17
|
+
let messageCount = core_1.START;
|
|
18
|
+
exports.SOCKET_CONNECTED = "SOCKET_CONNECTED";
|
|
19
|
+
function WebsocketAPI({ context, event, hass, config, lifecycle, logger, scheduler, }) {
|
|
20
|
+
let AUTH_TIMEOUT;
|
|
21
|
+
/**
|
|
22
|
+
* Local attachment points for socket events
|
|
23
|
+
*/
|
|
24
|
+
const socketEvents = new events_1.default();
|
|
25
|
+
event.setMaxListeners(UNLIMITED);
|
|
26
|
+
let connecting;
|
|
27
|
+
let MESSAGE_TIMESTAMPS = [];
|
|
28
|
+
const waitingCallback = new Map();
|
|
29
|
+
// Start the socket
|
|
30
|
+
lifecycle.onBootstrap(async () => {
|
|
31
|
+
if (config.hass.AUTO_CONNECT_SOCKET) {
|
|
32
|
+
logger.debug(`auto starting connection`);
|
|
33
|
+
await init();
|
|
34
|
+
attachScheduledFunctions();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
function attachScheduledFunctions() {
|
|
38
|
+
logger.trace(`attaching interval schedules`);
|
|
39
|
+
// Set up intervals
|
|
40
|
+
scheduler.interval({
|
|
41
|
+
exec: async () => await ping(),
|
|
42
|
+
interval: PING_INTERVAL * core_1.SECOND,
|
|
43
|
+
});
|
|
44
|
+
scheduler.interval({
|
|
45
|
+
exec: () => {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
MESSAGE_TIMESTAMPS = MESSAGE_TIMESTAMPS.filter(time => time > now - core_1.SECOND);
|
|
48
|
+
},
|
|
49
|
+
interval: CLEANUP_INTERVAL * core_1.SECOND,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
lifecycle.onShutdownComplete(async () => {
|
|
53
|
+
logger.debug(`shutdown - tearing down connection`);
|
|
54
|
+
await teardown();
|
|
55
|
+
});
|
|
56
|
+
async function ping() {
|
|
57
|
+
if (!CONNECTION_ACTIVE) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const pong = await sendMessage({ type: __1.HASSIO_WS_COMMAND.ping });
|
|
62
|
+
if (pong) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Tends to happen when HA resets
|
|
66
|
+
// Resolution is to re-connect when it's up again
|
|
67
|
+
logger.error(`failed to pong!`);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error({ error }, `ping error`);
|
|
71
|
+
}
|
|
72
|
+
logger.debug(`ping teardown`);
|
|
73
|
+
await teardown();
|
|
74
|
+
logger.info(`🪃 ping re-init`);
|
|
75
|
+
await init();
|
|
76
|
+
}
|
|
77
|
+
async function teardown() {
|
|
78
|
+
if (!connection) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (connection.readyState === CONNECTION_OPEN) {
|
|
82
|
+
logger.debug(`closing current connection`);
|
|
83
|
+
CONNECTION_ACTIVE = false;
|
|
84
|
+
connection.close();
|
|
85
|
+
}
|
|
86
|
+
connection = undefined;
|
|
87
|
+
}
|
|
88
|
+
async function fireEvent(event_type, event_data) {
|
|
89
|
+
// logger.debug({ event_data, event_type, type: "fire_event" });
|
|
90
|
+
return await sendMessage({ event_data, event_type, type: "fire_event" });
|
|
91
|
+
}
|
|
92
|
+
async function sendMessage(data, waitForResponse = true, subscription) {
|
|
93
|
+
if (!connection) {
|
|
94
|
+
logger.error("cannot send messages before socket is initialized");
|
|
95
|
+
logger.info("🪃 on error re-init");
|
|
96
|
+
await init();
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
countMessage();
|
|
100
|
+
if (data.type !== __1.HASSIO_WS_COMMAND.auth) {
|
|
101
|
+
if (!CONNECTION_ACTIVE) {
|
|
102
|
+
logger.error({ data }, `cannot send message, connection is not open`);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
data.id = messageCount;
|
|
106
|
+
}
|
|
107
|
+
const json = JSON.stringify(data);
|
|
108
|
+
connection.send(json);
|
|
109
|
+
if (subscription) {
|
|
110
|
+
return data.id;
|
|
111
|
+
}
|
|
112
|
+
if (!waitForResponse) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return new Promise(done => waitingCallback.set(messageCount, done));
|
|
116
|
+
}
|
|
117
|
+
function countMessage() {
|
|
118
|
+
messageCount++;
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
MESSAGE_TIMESTAMPS.push(now);
|
|
121
|
+
const count = MESSAGE_TIMESTAMPS.filter(time => time > now - core_1.SECOND).length;
|
|
122
|
+
if (count > config.hass.SOCKET_CRASH_REQUESTS_PER_SEC) {
|
|
123
|
+
logger.fatal(`FATAL ERROR: Exceeded {CRASH_REQUESTS_PER_MIN} threshold.`);
|
|
124
|
+
(0, process_1.exit)();
|
|
125
|
+
}
|
|
126
|
+
if (count > config.hass.SOCKET_WARN_REQUESTS_PER_SEC) {
|
|
127
|
+
logger.warn(`message traffic ${config.hass.SOCKET_CRASH_REQUESTS_PER_SEC}>${count}>${config.hass.SOCKET_WARN_REQUESTS_PER_SEC}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getUrl() {
|
|
131
|
+
const url = new URL(config.hass.BASE_URL);
|
|
132
|
+
const protocol = url.protocol === `http:` ? `ws:` : `wss:`;
|
|
133
|
+
const path = url.pathname === "/" ? "" : url.pathname;
|
|
134
|
+
const port = url.port ? `:${url.port}` : "";
|
|
135
|
+
return (config.hass.WEBSOCKET_URL ||
|
|
136
|
+
`${protocol}//${url.hostname}${port}${path}/api/websocket`);
|
|
137
|
+
}
|
|
138
|
+
async function init() {
|
|
139
|
+
const now = (0, dayjs_1.default)();
|
|
140
|
+
if (connecting) {
|
|
141
|
+
if (connecting.add(config.hass.RETRY_INTERVAL, "second").isAfter(now)) {
|
|
142
|
+
logger.debug("stopped, already connecting");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
connection = undefined;
|
|
146
|
+
}
|
|
147
|
+
if (connection) {
|
|
148
|
+
throw new core_1.InternalError(context, "ExistingConnection", `Destroy the current connection before creating a new one`);
|
|
149
|
+
}
|
|
150
|
+
connecting = now;
|
|
151
|
+
logger.debug(`CONNECTION_ACTIVE = {false}`);
|
|
152
|
+
const url = getUrl();
|
|
153
|
+
CONNECTION_ACTIVE = false;
|
|
154
|
+
try {
|
|
155
|
+
messageCount = core_1.START;
|
|
156
|
+
connection = new ws_1.default(url);
|
|
157
|
+
connection.on("message", async (message) => {
|
|
158
|
+
try {
|
|
159
|
+
await onMessage(JSON.parse(message.toString()));
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// My expectation is `ZCC.safeExec` should trap any application errors
|
|
163
|
+
// This try/catch should actually be excessive
|
|
164
|
+
// If this error happens, something weird is happening
|
|
165
|
+
logger.error({ error }, `💣 error bubbled up from websocket message event handler`);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
connection.on("error", async (error) => {
|
|
169
|
+
logger.error({ error: error.message || error }, "Socket error");
|
|
170
|
+
if (!CONNECTION_ACTIVE) {
|
|
171
|
+
await (0, core_1.sleep)(config.hass.RETRY_INTERVAL);
|
|
172
|
+
await teardown();
|
|
173
|
+
logger.info("🪃 on error re-init");
|
|
174
|
+
await (0, core_1.sleep)(core_1.SECOND);
|
|
175
|
+
await init();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
connection.on("close", async () => {
|
|
179
|
+
logger.warn("connection closed");
|
|
180
|
+
await teardown();
|
|
181
|
+
await (0, core_1.sleep)(config.hass.RETRY_INTERVAL);
|
|
182
|
+
logger.info("🪃 on close re-init");
|
|
183
|
+
await init();
|
|
184
|
+
});
|
|
185
|
+
return await new Promise(done => {
|
|
186
|
+
connection.once("open", () => {
|
|
187
|
+
connecting = undefined;
|
|
188
|
+
done();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
logger.error({ error, url }, `initConnection error`);
|
|
194
|
+
connecting = undefined;
|
|
195
|
+
setTimeout(async () => {
|
|
196
|
+
logger.debug(`🪃 retry re-init`);
|
|
197
|
+
await init();
|
|
198
|
+
}, config.hass.RETRY_INTERVAL);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Called on incoming message.
|
|
203
|
+
* Intended to interpret the basic concept of the message,
|
|
204
|
+
* and route it to the correct callback / global channel / etc
|
|
205
|
+
*
|
|
206
|
+
* ## auth_required
|
|
207
|
+
* Hello message from server, should reply back with an auth msg
|
|
208
|
+
* ## auth_ok
|
|
209
|
+
* Follow up with a request to receive all events, and request a current state listing
|
|
210
|
+
* ## event
|
|
211
|
+
* Something updated it's state
|
|
212
|
+
* ## pong
|
|
213
|
+
* Reply to outgoing ping()
|
|
214
|
+
* ## result
|
|
215
|
+
* Response to an outgoing emit
|
|
216
|
+
*/
|
|
217
|
+
async function onMessage(message) {
|
|
218
|
+
const id = Number(message.id);
|
|
219
|
+
switch (message.type) {
|
|
220
|
+
case __1.HassSocketMessageTypes.auth_required:
|
|
221
|
+
logger.debug(`sending authentication`);
|
|
222
|
+
return await sendAuth();
|
|
223
|
+
case __1.HassSocketMessageTypes.auth_ok:
|
|
224
|
+
logger.debug(`CONNECTION_ACTIVE = {true}`);
|
|
225
|
+
// * Flag as valid connection
|
|
226
|
+
CONNECTION_ACTIVE = true;
|
|
227
|
+
connecting = undefined;
|
|
228
|
+
event.emit(exports.SOCKET_CONNECTED);
|
|
229
|
+
clearTimeout(AUTH_TIMEOUT);
|
|
230
|
+
logger.debug(`event subscriptions starting`);
|
|
231
|
+
await sendMessage({ type: __1.HASSIO_WS_COMMAND.subscribe_events }, false);
|
|
232
|
+
event.emit(__1.ON_SOCKET_AUTH);
|
|
233
|
+
return;
|
|
234
|
+
case __1.HassSocketMessageTypes.event:
|
|
235
|
+
return await onMessageEvent(id, message);
|
|
236
|
+
case __1.HassSocketMessageTypes.pong:
|
|
237
|
+
// 🏓
|
|
238
|
+
if (waitingCallback.has(id)) {
|
|
239
|
+
const f = waitingCallback.get(id);
|
|
240
|
+
waitingCallback.delete(id);
|
|
241
|
+
f(message);
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
case __1.HassSocketMessageTypes.result:
|
|
245
|
+
return await onMessageResult(id, message);
|
|
246
|
+
case __1.HassSocketMessageTypes.auth_invalid:
|
|
247
|
+
logger.debug(`CONNECTION_ACTIVE = {false}`);
|
|
248
|
+
CONNECTION_ACTIVE = false;
|
|
249
|
+
logger.fatal(message.message);
|
|
250
|
+
return;
|
|
251
|
+
default:
|
|
252
|
+
// Code error probably
|
|
253
|
+
logger.error(`unknown websocket message type: ${message.type}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function onMessageEvent(id, message) {
|
|
257
|
+
if (message.event.event_type === "state_changed") {
|
|
258
|
+
const { new_state, old_state } = message.event.data;
|
|
259
|
+
const id = new_state?.entity_id || old_state.entity_id;
|
|
260
|
+
if (core_1.is.empty(id)) {
|
|
261
|
+
throw new core_1.InternalError(context, "NO_ID", "Received state change, but could not identify an entity_id");
|
|
262
|
+
}
|
|
263
|
+
// ? null = deleted entity
|
|
264
|
+
// TODO: handle renames properly
|
|
265
|
+
if (new_state || new_state === null) {
|
|
266
|
+
hass.entity[__1.ENTITY_UPDATE_RECEIVER](id, new_state, old_state);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// FIXME: probably removal / rename?
|
|
270
|
+
// It's an edge case for sure, and not positive this code should handle it
|
|
271
|
+
// If you have thoughts, chime in somewhere and we can do more sane things
|
|
272
|
+
logger.debug({ message }, `no new state for entity, what caused this?`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (waitingCallback.has(id)) {
|
|
277
|
+
const f = waitingCallback.get(id);
|
|
278
|
+
waitingCallback.delete(id);
|
|
279
|
+
f(message.event.result);
|
|
280
|
+
}
|
|
281
|
+
socketEvents.emit(message.event.event_type, message.event);
|
|
282
|
+
}
|
|
283
|
+
function onMessageResult(id, message) {
|
|
284
|
+
if (waitingCallback.has(id)) {
|
|
285
|
+
if (message.error) {
|
|
286
|
+
logger.error({ message });
|
|
287
|
+
}
|
|
288
|
+
const f = waitingCallback.get(id);
|
|
289
|
+
waitingCallback.delete(id);
|
|
290
|
+
f(message.result);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function sendAuth() {
|
|
294
|
+
AUTH_TIMEOUT = setTimeout(() => {
|
|
295
|
+
logger.error(`did not receive an auth response, retrying`);
|
|
296
|
+
sendAuth();
|
|
297
|
+
}, config.hass.RETRY_INTERVAL);
|
|
298
|
+
await sendMessage({
|
|
299
|
+
access_token: config.hass.TOKEN,
|
|
300
|
+
type: __1.HASSIO_WS_COMMAND.auth,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function onEvent({ context, label, event, once, exec, }) {
|
|
304
|
+
logger.trace({ context, event }, `attaching socket event listener`);
|
|
305
|
+
const callback = async (data) => {
|
|
306
|
+
await core_1.ZCC.safeExec({
|
|
307
|
+
duration: __1.SOCKET_EVENT_EXECUTION_TIME,
|
|
308
|
+
errors: __1.SOCKET_EVENT_ERRORS,
|
|
309
|
+
exec: async () => await exec(data),
|
|
310
|
+
executions: __1.SOCKET_EVENT_EXECUTION_COUNT,
|
|
311
|
+
labels: { context, event, label },
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
if (once) {
|
|
315
|
+
socketEvents.once(event, callback);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
socketEvents.on(event, callback);
|
|
319
|
+
}
|
|
320
|
+
return () => {
|
|
321
|
+
logger.trace({ context, event }, `removing socket event listener`);
|
|
322
|
+
socketEvents.removeListener(event, callback);
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
/**
|
|
327
|
+
* Convenient wrapper for sendMessage
|
|
328
|
+
*/
|
|
329
|
+
fireEvent,
|
|
330
|
+
getConnectionActive: () => CONNECTION_ACTIVE,
|
|
331
|
+
/**
|
|
332
|
+
* Set up a new websocket connection to home assistant
|
|
333
|
+
*
|
|
334
|
+
* This doesn't normally need to be called by applications, the extension self manages
|
|
335
|
+
*/
|
|
336
|
+
init,
|
|
337
|
+
onConnect: (callback) => {
|
|
338
|
+
event.on(exports.SOCKET_CONNECTED, async () => {
|
|
339
|
+
await core_1.ZCC.safeExec(async () => await callback());
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* Attach to the incoming stream of socket events. Do your own filtering and processing from there
|
|
344
|
+
*
|
|
345
|
+
* Returns removal function
|
|
346
|
+
*/
|
|
347
|
+
onEvent,
|
|
348
|
+
/**
|
|
349
|
+
* Send a message to home assistant via the socket connection
|
|
350
|
+
*
|
|
351
|
+
* Applications probably want a higher level function than this
|
|
352
|
+
*/
|
|
353
|
+
sendMessage,
|
|
354
|
+
/**
|
|
355
|
+
* remove the current socket connection to home assistant
|
|
356
|
+
*
|
|
357
|
+
* will need to call init() again to start up
|
|
358
|
+
*/
|
|
359
|
+
teardown,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
exports.WebsocketAPI = WebsocketAPI;
|
|
363
|
+
//# sourceMappingURL=websocket-api.extension.js.map
|