@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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/dist/dynamic.d.ts +1126 -0
  3. package/dist/dynamic.js +153 -0
  4. package/dist/dynamic.js.map +1 -0
  5. package/dist/extensions/call-proxy.extension.d.ts +4 -0
  6. package/dist/extensions/call-proxy.extension.js +88 -0
  7. package/dist/extensions/call-proxy.extension.js.map +1 -0
  8. package/dist/extensions/config.extension.d.ts +2 -0
  9. package/dist/extensions/config.extension.js +53 -0
  10. package/dist/extensions/config.extension.js.map +1 -0
  11. package/dist/extensions/entity-manager.extension.d.ts +61 -0
  12. package/dist/extensions/entity-manager.extension.js +212 -0
  13. package/dist/extensions/entity-manager.extension.js.map +1 -0
  14. package/dist/extensions/fetch-api.extension.d.ts +29 -0
  15. package/dist/extensions/fetch-api.extension.js +174 -0
  16. package/dist/extensions/fetch-api.extension.js.map +1 -0
  17. package/dist/extensions/index.d.ts +6 -0
  18. package/dist/extensions/index.js +10 -0
  19. package/dist/extensions/index.js.map +1 -0
  20. package/dist/extensions/utilities.extension.d.ts +9 -0
  21. package/dist/extensions/utilities.extension.js +43 -0
  22. package/dist/extensions/utilities.extension.js.map +1 -0
  23. package/dist/extensions/websocket-api.extension.d.ts +39 -0
  24. package/dist/extensions/websocket-api.extension.js +363 -0
  25. package/dist/extensions/websocket-api.extension.js.map +1 -0
  26. package/dist/hass.module.d.ts +80 -0
  27. package/dist/hass.module.js +83 -0
  28. package/dist/hass.module.js.map +1 -0
  29. package/dist/helpers/backup.helper.d.ts +11 -0
  30. package/dist/helpers/backup.helper.js +3 -0
  31. package/dist/helpers/backup.helper.js.map +1 -0
  32. package/dist/helpers/constants.helper.d.ts +54 -0
  33. package/dist/helpers/constants.helper.js +63 -0
  34. package/dist/helpers/constants.helper.js.map +1 -0
  35. package/dist/helpers/entity-state.helper.d.ts +45 -0
  36. package/dist/helpers/entity-state.helper.js +9 -0
  37. package/dist/helpers/entity-state.helper.js.map +1 -0
  38. package/dist/helpers/fetch/calendar.d.ts +54 -0
  39. package/dist/helpers/fetch/calendar.js +3 -0
  40. package/dist/helpers/fetch/calendar.js.map +1 -0
  41. package/dist/helpers/fetch/configuration.d.ts +34 -0
  42. package/dist/helpers/fetch/configuration.js +3 -0
  43. package/dist/helpers/fetch/configuration.js.map +1 -0
  44. package/dist/helpers/fetch/index.d.ts +4 -0
  45. package/dist/helpers/fetch/index.js +8 -0
  46. package/dist/helpers/fetch/index.js.map +1 -0
  47. package/dist/helpers/fetch/server-log.d.ts +10 -0
  48. package/dist/helpers/fetch/server-log.js +20 -0
  49. package/dist/helpers/fetch/server-log.js.map +1 -0
  50. package/dist/helpers/fetch/service-list.d.ts +51 -0
  51. package/dist/helpers/fetch/service-list.js +3 -0
  52. package/dist/helpers/fetch/service-list.js.map +1 -0
  53. package/dist/helpers/index.d.ts +7 -0
  54. package/dist/helpers/index.js +11 -0
  55. package/dist/helpers/index.js.map +1 -0
  56. package/dist/helpers/metrics.helper.d.ts +13 -0
  57. package/dist/helpers/metrics.helper.js +30 -0
  58. package/dist/helpers/metrics.helper.js.map +1 -0
  59. package/dist/helpers/utility.helper.d.ts +53 -0
  60. package/dist/helpers/utility.helper.js +30 -0
  61. package/dist/helpers/utility.helper.js.map +1 -0
  62. package/dist/helpers/websocket.helper.d.ts +129 -0
  63. package/dist/helpers/websocket.helper.js +3 -0
  64. package/dist/helpers/websocket.helper.js.map +1 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.js +8 -0
  67. package/dist/index.js.map +1 -0
  68. 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":";;;;AAAA,gDAY+B;AAC/B,0DAA0B;AAE1B,0BAaY;AAUZ,SAAgB,QAAQ,CAAC,EACvB,MAAM,EACN,SAAS,EACT,OAAO,EACP,MAAM,GACS;IACf,IAAI,OAAe,CAAC;IACpB,IAAI,UAAqB,CAAC;IAE1B,sBAAsB;IACtB,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE;QAC1B,MAAM,KAAK,GAAG,UAAG,CAAC,aAAa,CAAC;YAC9B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;YAC7B,OAAO;YACP,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE;SAC1D,CAAC,CAAC;QACH,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC;QACtB,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC;IAC9B,CAAC,EAAE,wBAAoB,CAAC,KAAK,CAAC,CAAC;IAE/B,KAAK,UAAU,cAAc,CAAC,EAC5B,QAAQ,EACR,KAAK,GAAG,IAAA,eAAK,GAAE,EACf,GAAG,GACkB;QACrB,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAC5B,QAAQ,CAAC,GAAG,CACV,KAAK,EAAC,GAAG,EAAC,EAAE,CAAC,MAAM,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CACjE,CACF,CAAC;YACF,OAAO,IAAI;iBACR,IAAI,EAAE;iBACN,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACb,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;gBACrB,CAAC,CAAC,gBAAS;gBACX,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;oBACxB,CAAC,CAAC,SAAE;oBACJ,CAAC,CAAC,WAAI,CACX,CAAC;QACN,CAAC;QAED,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAqB;YAC/C,MAAM;YACN,GAAG,EAAE,kBAAkB,QAAQ,EAAE;SAClC,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CACV,EAAE,GAAG,MAAM,EAAE,EACb,2BAA2B,EAC3B,QAAQ,EACR,MAAM,CAAC,MAAM,CACd,CAAC;QACF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/C,GAAG,KAAK;YACR,GAAG,EAAE,IAAA,eAAK,EAAC,GAAG,CAAC,QAAQ,CAAC;YACxB,KAAK,EAAE,IAAA,eAAK,EAAC,KAAK,CAAC,QAAQ,CAAC;SAC7B,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,UAAU,WAAW,CACxB,WAAoB,EACpB,IAAsC;QAEtC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjD,OAAO,MAAM,OAAO,CAAC;YACnB,IAAI,EAAE,IAAkB;YACxB,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,iBAAiB,MAAM,IAAI,OAAO,EAAE;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,WAAW;QACxB,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC7B,OAAO,MAAM,OAAO,CAAC;YACnB,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,+BAA+B;SACrC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,QAAQ,CACrB,WAAmB,EACnB,SAAiC;QAEjC,MAAM,UAAU,CAAC;YACf,GAAG,SAAS;YACZ,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;YAC7B,WAAW;YACX,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE;SAC1D,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,yBAAyB,CAKtC,QAA2B;QAC3B,OAAO,MAAM,OAAO,CAAI;YACtB,GAAG,EAAE,gCAAgC,QAAQ,EAAE;SAChD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,kBAAkB,CAI/B,SAAiB,EACjB,IAAU,EACV,EAAQ,EACR,QAAmC,EAAE;QAErC,MAAM,CAAC,IAAI,CACT,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,EAC7D,sBAAsB,CACvB,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAQ;YAClC,MAAM,EAAE;gBACN,QAAQ,EAAE,EAAE,CAAC,WAAW,EAAE;gBAC1B,gBAAgB,EAAE,SAAS;gBAC3B,GAAG,KAAK;aACT;YACD,GAAG,EAAE,uBAAuB,IAAI,CAAC,WAAW,EAAE,EAAE;SACjD,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,0BAA0B,CAAC,CAAC;YACrD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,UAAU,SAAS,CACtB,KAAa,EACb,IAAW;QAEX,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,cAAc,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAsB;YAClD,cAAc;YACd,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,eAAe,KAAK,EAAE;SAC5B,CAAC,CAAC;QACH,IAAI,QAAQ,EAAE,OAAO,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,uCAAuC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,KAAK,UAAU,cAAc;QAC3B,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACjC,OAAO,MAAM,OAAO,CAA8B,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,UAAU,aAAa;QAC1B,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC3B,OAAO,MAAM,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,UAAU,OAAO;QACpB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,OAAO,CAA+B;YAC1D,GAAG,EAAE,gBAAgB;SACtB,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YACrB,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,aAAM,CAAC,CAAC;YAC/C,CAAC,CAAC,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,GAAG,aAAM,CAAC,CAAC;YACzD,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,UAAU;QACvB,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC7B,OAAO,MAAM,OAAO,CAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,UAAU,YAAY;QACzB,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAC9B,OAAO,MAAM,OAAO,CAAmB,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,UAAU,YAAY,CAIzB,SAAsB,EACtB,EAAE,UAAU,EAAE,KAAK,EAA+B;QAElD,MAAM,IAAI,GAAoB,EAAE,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,SAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC/B,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,kBAAkB,CAAC,CAAC;QAC/D,MAAM,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,SAAS,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,OAAe,EAAE;QACpD,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC;QAC3C,MAAM,OAAO,CAAC;YACZ,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,gBAAgB,IAAI,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,gBAAgB;QAC7B,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAClC,OAAO,MAAM,OAAO,CAAC;YACnB,GAAG,EAAE,OAAO;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,cAAc;QACd,WAAW;QACX,WAAW;QACX,gBAAgB;QAChB,QAAQ;QACR,KAAK,EAAE,OAAO;QACd,yBAAyB;QACzB,kBAAkB;QAClB,SAAS;QACT,cAAc;QACd,SAAS,EAAE,aAAa;QACxB,OAAO;QACP,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,OAAO;KACR,CAAC;AACJ,CAAC;AAzOD,4BAyOC"}
@@ -0,0 +1,6 @@
1
+ export * from "./call-proxy.extension";
2
+ export * from "./config.extension";
3
+ export * from "./entity-manager.extension";
4
+ export * from "./fetch-api.extension";
5
+ export * from "./utilities.extension";
6
+ export * from "./websocket-api.extension";
@@ -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