Compare commits

...

370 commits

Author SHA1 Message Date
dependabot[bot]
1a35138ed2
Bump eslint from 8.50.0 to 8.51.0 (#408) 2023-10-09 01:05:12 +00:00
dependabot[bot]
898adfbdc9
Bump zod from 3.22.3 to 3.22.4 (#406) 2023-10-05 01:01:33 +00:00
dependabot[bot]
c1cbbc07ab
Bump zod from 3.22.2 to 3.22.3 (#405) 2023-10-04 01:01:21 +00:00
dependabot[bot]
709f74baf8
Bump @types/supertest from 2.0.13 to 2.0.14 (#403) 2023-10-03 00:56:46 +00:00
dependabot[bot]
c09be24f0f
Bump rimraf from 5.0.4 to 5.0.5 (#400) 2023-09-28 01:02:24 +00:00
dependabot[bot]
50fd37ef54
Bump release-it from 16.2.0 to 16.2.1 (#398) 2023-09-28 01:01:16 +00:00
dependabot[bot]
3b582ea7b8
Bump @types/supertest from 2.0.12 to 2.0.13 (#396) 2023-09-26 00:58:47 +00:00
dependabot[bot]
3fed2ecf7d
Bump rimraf from 5.0.1 to 5.0.4 (#394) 2023-09-26 00:57:58 +00:00
dependabot[bot]
58e71351ab
Bump actions/checkout from 4.0.0 to 4.1.0 (#393) 2023-09-25 00:51:20 +00:00
dependabot[bot]
a0a8f7301f
Bump eslint from 8.49.0 to 8.50.0 (#391) 2023-09-25 00:50:51 +00:00
dependabot[bot]
9e789fc347
Bump eslint-import-resolver-typescript from 3.6.0 to 3.6.1 (#390) 2023-09-25 00:27:51 +00:00
dependabot[bot]
5925fedb53
Bump release-it from 16.1.5 to 16.2.0 (#389) 2023-09-25 00:27:21 +00:00
dependabot[bot]
f382e4e9a8
Bump jest from 29.6.4 to 29.7.0 (#382) 2023-09-13 00:33:33 +00:00
dependabot[bot]
11f69ceb55
Bump eslint from 8.48.0 to 8.49.0 (#379) 2023-09-11 00:26:34 +00:00
dependabot[bot]
2849cd732c
Bump array.prototype.flatmap from 1.3.1 to 1.3.2 (#377) 2023-09-08 00:55:55 +00:00
dependabot[bot]
92f92e81fd
Bump actions/cache from 3.3.1 to 3.3.2 (#376) 2023-09-08 00:47:34 +00:00
dependabot[bot]
99e03d73d0
Bump actions/upload-artifact from 3.1.2 to 3.1.3 (#375) 2023-09-07 01:06:29 +00:00
dependabot[bot]
ef35642f10
Bump actions/checkout from 3.6.0 to 4.0.0 (#373) 2023-09-05 00:13:36 +00:00
dependabot[bot]
c37fad86f6
Bump eslint from 8.47.0 to 8.48.0 (#365) 2023-08-28 00:47:37 +00:00
dependabot[bot]
1349b4da25
Bump actions/checkout from 3.5.3 to 3.6.0 (#364) 2023-08-25 00:32:44 +00:00
dependabot[bot]
c5c1434c77
Bump jest from 29.6.3 to 29.6.4 (#362) 2023-08-25 00:22:49 +00:00
dependabot[bot]
26188855bb
Bump typescript from 5.1.6 to 5.2.2 (#363) 2023-08-25 00:22:06 +00:00
dependabot[bot]
4f80e744b2
Bump @jest/globals from 29.6.3 to 29.6.4 (#360) 2023-08-25 00:18:13 +00:00
dependabot[bot]
dac42d5ead
Bump @jest/globals from 29.6.2 to 29.6.3 (#357) 2023-08-22 00:12:57 +00:00
dependabot[bot]
3589832ad6
Bump jest from 29.6.2 to 29.6.3 (#356) 2023-08-22 00:12:18 +00:00
dependabot[bot]
54cf7f5f24
Bump zod from 3.22.1 to 3.22.2 (#354) 2023-08-21 01:13:03 +00:00
dependabot[bot]
b73f025548
Bump eslint-plugin-import from 2.28.0 to 2.28.1 (#353) 2023-08-21 01:11:51 +00:00
dependabot[bot]
fce96663db
Bump actions/setup-node from 3.8.0 to 3.8.1 (#351) 2023-08-18 01:06:54 +00:00
dependabot[bot]
064d09ff70
Bump bcrypt from 5.1.0 to 5.1.1 (#350) 2023-08-17 00:35:30 +00:00
dependabot[bot]
af0296b7b3
Bump zod from 3.22.0 to 3.22.1 (#348) 2023-08-16 00:22:43 +00:00
dependabot[bot]
3079103d03
Bump zod from 3.21.4 to 3.22.0 (#347) 2023-08-15 00:55:52 +00:00
dependabot[bot]
3f15bae6a8
Bump actions/setup-node from 3.7.0 to 3.8.0 (#345) 2023-08-15 00:54:11 +00:00
dependabot[bot]
f72862b955
Bump release-it from 16.1.4 to 16.1.5 (#343) 2023-08-14 01:13:09 +00:00
dependabot[bot]
3bd5d1da4d
Bump eslint from 8.46.0 to 8.47.0 (#342) 2023-08-14 01:12:17 +00:00
dependabot[bot]
9f94d50f1e
Bump eslint-import-resolver-typescript from 3.5.5 to 3.6.0 (#341) 2023-08-10 00:54:44 +00:00
dependabot[bot]
cedf9b7af7
Bump release-it from 16.1.3 to 16.1.4 (#340) 2023-08-10 00:53:16 +00:00
dependabot[bot]
2b093b0fa2
Bump @types/node from 20.4.8 to 20.4.9 (#339) 2023-08-09 00:38:05 +00:00
dependabot[bot]
ac5eb866d8
Bump @types/node from 20.4.7 to 20.4.8 (#337) 2023-08-07 00:56:59 +00:00
dependabot[bot]
c0654ce76f
Bump @types/node from 20.4.6 to 20.4.7 (#335) 2023-08-04 00:58:20 +00:00
dependabot[bot]
52857f987c
Bump @types/node from 20.4.5 to 20.4.6 (#334) 2023-08-03 01:01:08 +00:00
dependabot[bot]
37246fa512
Bump eslint from 8.45.0 to 8.46.0 (#332) 2023-07-31 01:03:24 +00:00
dependabot[bot]
06222b4846
Bump eslint-plugin-import from 2.27.5 to 2.28.0 (#331) 2023-07-31 00:26:00 +00:00
dependabot[bot]
73d4b44522
Bump jest from 29.6.1 to 29.6.2 (#330) 2023-07-28 00:25:31 +00:00
dependabot[bot]
9df9d347ad
Bump @jest/globals from 29.6.1 to 29.6.2 (#329) 2023-07-28 00:21:50 +00:00
dependabot[bot]
9379ec63fa
Bump fastify from 4.20.0 to 4.21.0 (#328) 2023-07-28 00:20:58 +00:00
dependabot[bot]
0e29b88c32
Bump @types/node from 20.4.4 to 20.4.5 (#327) 2023-07-26 00:48:09 +00:00
dependabot[bot]
2363bf25c4
Bump @types/node from 20.4.2 to 20.4.4 (#325) 2023-07-24 00:56:40 +00:00
dependabot[bot]
d5b3fbdbb1
Bump release-it from 16.1.2 to 16.1.3 (#324) 2023-07-21 00:58:15 +00:00
dependabot[bot]
c36a5af463
Bump word-wrap from 1.2.3 to 1.2.4 (#323) 2023-07-20 01:12:40 +00:00
dependabot[bot]
2b859d8322
Bump fastify from 4.19.2 to 4.20.0 (#321) 2023-07-18 00:27:45 +00:00
dependabot[bot]
06c48fb317
Bump eslint from 8.44.0 to 8.45.0 (#320) 2023-07-17 00:49:10 +00:00
dependabot[bot]
a72d5948ab
Bump release-it from 16.1.0 to 16.1.2 (#319) 2023-07-17 00:48:06 +00:00
dependabot[bot]
440b8455d3
Bump @types/node from 20.4.1 to 20.4.2 (#317) 2023-07-13 00:37:18 +00:00
dependabot[bot]
fcb3767093
Bump semver from 6.3.0 to 6.3.1 (#316) 2023-07-11 00:31:05 +00:00
dependabot[bot]
40c0a61ba6
Bump @typescript-eslint/parser from 5.61.0 to 5.62.0 (#315) 2023-07-11 00:28:20 +00:00
dependabot[bot]
9d9fbfb6b7
Bump @types/node from 20.4.0 to 20.4.1 (#312) 2023-07-10 00:33:23 +00:00
dependabot[bot]
96084208bf
Bump nodemon from 2.0.22 to 3.0.1 (#311) 2023-07-10 00:33:01 +00:00
dependabot[bot]
60373593bf
Bump release-it from 16.0.0 to 16.1.0 (#310) 2023-07-10 00:32:52 +00:00
dependabot[bot]
84b75d8c24
Bump jest from 29.6.0 to 29.6.1 (#309) 2023-07-07 01:08:39 +00:00
dependabot[bot]
f7a753a86d
Bump @jest/globals from 29.6.0 to 29.6.1 (#308) 2023-07-07 01:05:14 +00:00
dependabot[bot]
cc162d9a56
Bump actions/setup-node from 3.6.0 to 3.7.0 (#307) 2023-07-06 00:51:21 +00:00
dependabot[bot]
704a353667
Bump release-it from 15.11.0 to 16.0.0 (#306) 2023-07-06 00:31:36 +00:00
dependabot[bot]
fc838ad93e
Bump @types/node from 20.3.3 to 20.4.0 (#304) 2023-07-06 00:30:10 +00:00
dependabot[bot]
303f397c83
Bump jest from 29.5.0 to 29.6.0 (#303) 2023-07-05 00:32:43 +00:00
dependabot[bot]
72e7d6310d
Bump @jest/globals from 29.5.0 to 29.6.0 (#302) 2023-07-05 00:29:27 +00:00
dependabot[bot]
8425ab99db
Bump @typescript-eslint/parser from 5.60.1 to 5.61.0 (#300) 2023-07-04 00:33:37 +00:00
dependabot[bot]
455844a4ed
Bump fastify from 4.19.1 to 4.19.2 (#301) 2023-07-04 00:32:05 +00:00
dependabot[bot]
e5d9b7bfb9
Bump @typescript-eslint/eslint-plugin from 5.60.1 to 5.61.0 (#299) 2023-07-04 00:30:08 +00:00
dependabot[bot]
fdbcfc27cf
Bump eslint from 8.43.0 to 8.44.0 (#298) 2023-07-03 00:24:19 +00:00
dependabot[bot]
2641d455c1
Bump @types/node from 20.3.2 to 20.3.3 (#297) 2023-07-03 00:23:21 +00:00
dependabot[bot]
d15fcfe557
Bump fastify from 4.18.0 to 4.19.1 (#296) 2023-07-03 00:22:40 +00:00
dependabot[bot]
2466225145
Bump ts-jest from 29.1.0 to 29.1.1 (#295) 2023-07-03 00:22:25 +00:00
dependabot[bot]
91592d5e21
Bump typescript from 5.1.5 to 5.1.6 (#294) 2023-06-29 00:51:55 +00:00
dependabot[bot]
77169d3f06
Bump typescript from 5.1.3 to 5.1.5 (#293) 2023-06-28 01:08:22 +00:00
dependabot[bot]
366e31ce17
Bump @typescript-eslint/eslint-plugin from 5.60.0 to 5.60.1 (#292) 2023-06-27 01:08:01 +00:00
dependabot[bot]
543c130543
Bump @types/node from 20.3.1 to 20.3.2 (#291) 2023-06-27 01:07:31 +00:00
dependabot[bot]
b7e0eae52f
Bump @typescript-eslint/parser from 5.60.0 to 5.60.1 (#290) 2023-06-27 01:06:12 +00:00
dependabot[bot]
3d78d1cbc3
Bump @typescript-eslint/parser from 5.59.11 to 5.60.0 (#289) 2023-06-20 01:10:08 +00:00
dependabot[bot]
bba1ceb04a
Bump @typescript-eslint/eslint-plugin from 5.59.11 to 5.60.0 (#288) 2023-06-20 01:06:48 +00:00
dependabot[bot]
27d9c954ba
Bump eslint from 8.42.0 to 8.43.0 (#287) 2023-06-19 01:36:57 +00:00
dependabot[bot]
3d31ca39ea
Bump @types/node from 20.3.0 to 20.3.1 (#286) 2023-06-14 01:10:12 +00:00
dependabot[bot]
995cec0974
Bump @typescript-eslint/eslint-plugin from 5.59.9 to 5.59.11 (#285) 2023-06-13 01:07:19 +00:00
dependabot[bot]
9fae4246d0
Bump @typescript-eslint/parser from 5.59.9 to 5.59.11 (#284) 2023-06-13 01:06:14 +00:00
dependabot[bot]
7db8afc51c
Bump @types/node from 20.2.5 to 20.3.0 (#283) 2023-06-12 01:38:27 +00:00
dependabot[bot]
e0f3d4ca87
Bump fastify from 4.17.0 to 4.18.0 (#282) 2023-06-12 01:37:32 +00:00
dependabot[bot]
3a6fe1390d
Bump actions/checkout from 3.5.2 to 3.5.3 (#281) 2023-06-12 01:37:07 +00:00
dependabot[bot]
eca3bff6c3
Bump @typescript-eslint/parser from 5.59.8 to 5.59.9 (#280) 2023-06-06 01:10:28 +00:00
dependabot[bot]
7b110d609b
Bump @typescript-eslint/eslint-plugin from 5.59.8 to 5.59.9 (#279) 2023-06-06 01:07:35 +00:00
dependabot[bot]
486587fc86
Bump release-it from 15.10.5 to 15.11.0 (#278) 2023-06-06 01:06:03 +00:00
dependabot[bot]
c54cd9b871
Bump release-it from 15.10.3 to 15.10.5 (#277) 2023-06-05 01:40:09 +00:00
dependabot[bot]
b4210ec85d
Bump eslint from 8.41.0 to 8.42.0 (#276) 2023-06-05 01:38:48 +00:00
dependabot[bot]
252fab726a
Bump typescript from 5.0.4 to 5.1.3 (#275) 2023-06-02 01:06:49 +00:00
dependabot[bot]
e7343bd194
Bump @typescript-eslint/eslint-plugin from 5.59.7 to 5.59.8 (#274) 2023-05-30 01:06:36 +00:00
dependabot[bot]
c4626d0186
Bump @typescript-eslint/parser from 5.59.7 to 5.59.8 (#273) 2023-05-30 01:05:09 +00:00
dependabot[bot]
03843eacf2
Bump @types/node from 20.2.4 to 20.2.5 (#271) 2023-05-27 11:26:29 +00:00
Lars Strojny
7b0be18923
Setup dependabot automerge 2023-05-27 13:23:21 +02:00
dependabot[bot]
9ce1163bc0
Bump @types/node from 20.2.3 to 20.2.4 (#269) 2023-05-26 09:37:06 +02:00
dependabot[bot]
717b27c4e2
Bump @typescript-eslint/eslint-plugin from 5.59.6 to 5.59.7 (#267) 2023-05-25 18:08:52 +02:00
dependabot[bot]
945568d91c
Bump @typescript-eslint/parser from 5.59.6 to 5.59.7 (#268) 2023-05-25 18:08:43 +02:00
dependabot[bot]
ae9dc17e0f
Bump hap-nodejs from 0.11.0 to 0.11.1 (#239) 2023-05-22 16:03:50 +02:00
dependabot[bot]
c18d79e662
Bump homebridge from 1.6.0 to 1.6.1 (#241) 2023-05-22 16:03:42 +02:00
dependabot[bot]
17bcf3d4cf
Bump @typescript-eslint/eslint-plugin from 5.59.1 to 5.59.6 (#258) 2023-05-22 15:54:01 +02:00
dependabot[bot]
c8b70eeb27
Bump @typescript-eslint/parser from 5.59.1 to 5.59.6 (#256) 2023-05-22 15:53:51 +02:00
dependabot[bot]
a8c4a19808
Bump release-it from 15.10.1 to 15.10.3 (#245) 2023-05-22 15:45:50 +02:00
dependabot[bot]
05ad26f999
Bump xml2js and @homebridge/dbus-native (#255) 2023-05-22 15:45:40 +02:00
dependabot[bot]
d1cf71869e
Bump vm2 from 3.9.17 to 3.9.19 (#260) 2023-05-22 15:45:06 +02:00
dependabot[bot]
d2cfdef13a
Bump rimraf from 5.0.0 to 5.0.1 (#261) 2023-05-22 15:44:57 +02:00
dependabot[bot]
539c202a83
Bump @fastify/auth from 4.2.0 to 4.3.0 (#264) 2023-05-22 15:44:19 +02:00
dependabot[bot]
d63f3fb465
Bump @types/node from 18.16.1 to 20.2.3 (#265) 2023-05-22 15:44:03 +02:00
dependabot[bot]
6d217c3e95
Bump eslint from 8.39.0 to 8.41.0 (#266) 2023-05-22 15:43:51 +02:00
dependabot[bot]
d3f77a7180
Bump fastify from 4.15.0 to 4.17.0 (#238) 2023-05-22 15:43:40 +02:00
dependabot[bot]
be61660ab7
Bump @typescript-eslint/parser from 5.58.0 to 5.59.1 (#232) 2023-04-26 13:19:16 +02:00
dependabot[bot]
9126d38fea
Bump @typescript-eslint/eslint-plugin from 5.58.0 to 5.59.1 (#233) 2023-04-26 09:25:38 +02:00
dependabot[bot]
5515aaf590
Bump @types/node from 18.16.0 to 18.16.1 (#235) 2023-04-26 09:22:54 +02:00
dependabot[bot]
2d5ba1d8f1
Bump actions/checkout from 3.5.0 to 3.5.2 (#224) 2023-04-25 14:59:59 +02:00
dependabot[bot]
92155ae9b2
Bump vm2 from 3.9.15 to 3.9.17 (#228) 2023-04-25 14:59:50 +02:00
dependabot[bot]
e216174dce
Bump eslint from 8.38.0 to 8.39.0 (#229) 2023-04-25 14:59:24 +02:00
dependabot[bot]
dd983f9926
Bump prettier from 2.8.7 to 2.8.8 (#230) 2023-04-25 14:59:16 +02:00
dependabot[bot]
dc4c8d8d39
Bump @types/node from 18.15.11 to 18.16.0 (#231) 2023-04-25 14:59:05 +02:00
dependabot[bot]
ec2025b768
Bump vm2 from 3.9.14 to 3.9.15 (#216) 2023-04-11 13:31:53 +02:00
dependabot[bot]
c3f519ae4a
Bump typescript from 5.0.3 to 5.0.4 (#217) 2023-04-11 13:31:46 +02:00
dependabot[bot]
acfb6e689e
Bump rimraf from 4.4.1 to 5.0.0 (#218) 2023-04-11 13:31:38 +02:00
dependabot[bot]
ccb89a9889
Bump eslint from 8.37.0 to 8.38.0 (#219) 2023-04-11 13:31:30 +02:00
dependabot[bot]
cb77478841
Bump @typescript-eslint/eslint-plugin from 5.57.1 to 5.58.0 (#220) 2023-04-11 13:31:20 +02:00
dependabot[bot]
20b362a90c
Bump @typescript-eslint/parser from 5.57.1 to 5.58.0 (#221) 2023-04-11 13:31:11 +02:00
dependabot[bot]
c392a82084
Bump eslint-import-resolver-typescript from 3.5.4 to 3.5.5 (#215) 2023-04-06 15:45:58 +02:00
dependabot[bot]
cbc6d971ac
Bump @typescript-eslint/parser from 5.57.0 to 5.57.1 (#212) 2023-04-04 12:44:43 +02:00
dependabot[bot]
409c477d63
Bump @typescript-eslint/eslint-plugin from 5.57.0 to 5.57.1 (#213) 2023-04-04 12:22:04 +02:00
dependabot[bot]
ea238c97f6
Bump release-it from 15.10.0 to 15.10.1 (#214) 2023-04-04 12:21:58 +02:00
dependabot[bot]
5ed6ee7d1e
Bump release-it from 15.9.3 to 15.10.0 (#211) 2023-04-03 14:56:17 +02:00
dependabot[bot]
1c57adc4a0
Bump ts-jest from 29.0.5 to 29.1.0 (#210) 2023-04-03 14:56:11 +02:00
dependabot[bot]
890d1ef780
Bump typescript from 5.0.2 to 5.0.3 (#209) 2023-03-31 11:12:36 +02:00
dependabot[bot]
92f88d1fe9
Bump eslint-import-resolver-typescript from 3.5.3 to 3.5.4 (#208) 2023-03-30 08:14:37 +02:00
dependabot[bot]
bad4fa08b5
Bump release-it from 15.9.1 to 15.9.3 (#203) 2023-03-30 01:10:39 +02:00
dependabot[bot]
8646f816fc
Bump prettier from 2.8.6 to 2.8.7 (#200) 2023-03-29 22:45:22 +02:00
dependabot[bot]
47eb77c6fd
Bump actions/checkout from 3.4.0 to 3.5.0 (#202) 2023-03-29 08:19:36 +02:00
dependabot[bot]
796eec97c2
Bump @typescript-eslint/eslint-plugin from 5.56.0 to 5.57.0 (#204) 2023-03-29 08:17:05 +02:00
dependabot[bot]
b4512f25cc
Bump @typescript-eslint/parser from 5.56.0 to 5.57.0 (#205) 2023-03-29 08:15:02 +02:00
dependabot[bot]
c2997d04d5
Bump @types/node from 18.15.6 to 18.15.11 (#206) 2023-03-29 08:13:30 +02:00
dependabot[bot]
9c4964c1ae
Bump eslint from 8.36.0 to 8.37.0 (#207) 2023-03-29 08:13:19 +02:00
dependabot[bot]
1ead5e54ef
Bump @types/node from 18.15.5 to 18.15.6 (#199) 2023-03-24 08:16:44 +01:00
dependabot[bot]
c73299f7b1
Bump rimraf from 4.4.0 to 4.4.1 (#197) 2023-03-23 13:52:34 +01:00
dependabot[bot]
5d8dee811a
Bump nodemon from 2.0.21 to 2.0.22 (#198) 2023-03-23 13:52:15 +01:00
dependabot[bot]
d176ad2c9d
Bump release-it from 15.9.0 to 15.9.1 (#196) 2023-03-22 05:31:35 +01:00
dependabot[bot]
d1a39d9838
Bump prettier from 2.8.5 to 2.8.6 (#195) 2023-03-22 05:31:01 +01:00
dependabot[bot]
a85bf04d0e
Bump @typescript-eslint/eslint-plugin from 5.55.0 to 5.56.0 (#191) 2023-03-21 22:32:46 +01:00
dependabot[bot]
0ccc4a8579
Bump @types/node from 18.15.3 to 18.15.5 (#194) 2023-03-21 21:25:31 +01:00
dependabot[bot]
719ff9edd1
Bump prettier from 2.8.4 to 2.8.5 (#193) 2023-03-21 21:25:23 +01:00
dependabot[bot]
106daf044b
Bump @typescript-eslint/parser from 5.55.0 to 5.56.0 (#192) 2023-03-21 21:25:12 +01:00
dependabot[bot]
f97a52b0a7
Bump fastify from 4.14.1 to 4.15.0 (#190) 2023-03-21 21:25:03 +01:00
dependabot[bot]
c07b8fdff8
Bump typescript from 4.9.5 to 5.0.2 (#188) 2023-03-19 23:54:57 +01:00
dependabot[bot]
84b23d6b14
Bump release-it from 15.8.0 to 15.9.0 (#189) 2023-03-17 07:29:23 +01:00
dependabot[bot]
9446ff2a16
Bump actions/checkout from 3.3.0 to 3.4.0 (#187) 2023-03-16 07:23:02 +01:00
dependabot[bot]
92594538fa
Bump @typescript-eslint/eslint-plugin from 5.54.1 to 5.55.0 (#182) 2023-03-15 19:55:33 +01:00
dependabot[bot]
37a8545c5e
Bump @typescript-eslint/parser from 5.54.1 to 5.55.0 (#183) 2023-03-15 19:55:17 +01:00
dependabot[bot]
47a097cc4d
Bump actions/cache from 3.3.0 to 3.3.1 (#184) 2023-03-15 19:55:08 +01:00
dependabot[bot]
b5f8df30d8
Bump @types/node from 18.15.0 to 18.15.3 (#186) 2023-03-15 19:55:00 +01:00
dependabot[bot]
df991fe5e5
Bump eslint from 8.35.0 to 8.36.0 (#181) 2023-03-13 07:32:19 +01:00
dependabot[bot]
eba5c42871
Bump rimraf from 4.3.1 to 4.4.0 (#177) 2023-03-12 10:34:04 +01:00
dependabot[bot]
87721ec82a
Bump release-it from 15.7.0 to 15.8.0 (#178) 2023-03-12 10:33:03 +01:00
dependabot[bot]
d76faf9197
Bump actions/cache from 3.2.6 to 3.3.0 (#179) 2023-03-12 10:30:55 +01:00
dependabot[bot]
dc71ce5ca4
Bump @types/node from 18.14.6 to 18.15.0 (#180) 2023-03-12 10:30:11 +01:00
dependabot[bot]
579e973c66
Bump fastify from 4.13.0 to 4.14.1 (#176) 2023-03-07 21:46:29 +01:00
dependabot[bot]
9ea9576aa2
Bump @types/node from 18.14.4 to 18.14.6 (#169) 2023-03-07 18:56:03 +01:00
dependabot[bot]
b88630e1ea
Bump rimraf from 4.2.0 to 4.3.1 (#170) 2023-03-07 18:50:45 +01:00
dependabot[bot]
82983a2827
Bump jest from 29.4.3 to 29.5.0 (#174) 2023-03-07 18:50:32 +01:00
dependabot[bot]
0a58df5386
Bump @jest/globals from 29.4.3 to 29.5.0 (#175) 2023-03-07 18:50:24 +01:00
dependabot[bot]
789f0ffea8
Bump @typescript-eslint/eslint-plugin from 5.54.0 to 5.54.1 (#173) 2023-03-07 18:50:16 +01:00
dependabot[bot]
3063ea575c
Bump @typescript-eslint/parser from 5.54.0 to 5.54.1 (#172) 2023-03-07 18:50:09 +01:00
dependabot[bot]
ddac01aea3
Bump zod from 3.20.6 to 3.21.3 (#171) 2023-03-07 18:49:51 +01:00
dependabot[bot]
d4d0f412e4
Bump rimraf from 4.1.3 to 4.2.0 (#164) 2023-03-03 06:08:29 +01:00
dependabot[bot]
5e0472c1f6
Bump nodemon from 2.0.20 to 2.0.21 (#163) 2023-03-03 05:41:18 +01:00
dependabot[bot]
b254a33d47
Bump @types/node from 18.14.2 to 18.14.4 (#165) 2023-03-03 05:40:59 +01:00
dependabot[bot]
b3eea21ecb
Bump eslint from 8.34.0 to 8.35.0 (#157) 2023-03-02 22:32:39 +01:00
dependabot[bot]
454e3d1e89
Bump @typescript-eslint/eslint-plugin from 5.53.0 to 5.54.0 (#158) 2023-03-02 20:14:13 +01:00
dependabot[bot]
2a578c5d1b
Bump release-it from 15.6.1 to 15.7.0 (#161) 2023-03-02 20:14:05 +01:00
dependabot[bot]
0131f8a3f7
Bump rimraf from 4.1.2 to 4.1.3 (#162) 2023-03-02 20:13:56 +01:00
dependabot[bot]
9bce041750
Bump @typescript-eslint/parser from 5.53.0 to 5.54.0 (#159) 2023-03-01 16:25:05 +01:00
dependabot[bot]
6f20f49855
Bump @types/node from 18.14.0 to 18.14.2 (#156) 2023-03-01 07:56:23 +01:00
dependabot[bot]
fe6804d1e9
Bump release-it from 15.6.0 to 15.6.1 (#160) 2023-03-01 07:51:13 +01:00
dependabot[bot]
4008da12a8
Bump @types/node from 18.13.0 to 18.14.0 (#152) 2023-02-23 23:19:59 +01:00
dependabot[bot]
49291172ac
Bump @typescript-eslint/eslint-plugin from 5.52.0 to 5.53.0 (#153) 2023-02-23 23:19:03 +01:00
dependabot[bot]
daf064caee
Bump @typescript-eslint/parser from 5.52.0 to 5.53.0 (#154) 2023-02-23 23:18:26 +01:00
dependabot[bot]
ad5fe8b853
Bump actions/cache from 3.2.5 to 3.2.6 (#155) 2023-02-23 23:17:50 +01:00
Lars Strojny
970b7129c0
PayPal sponsorship 2023-02-18 15:04:40 +01:00
Lars Strojny
520725ecd9
Setup direnv 2023-02-18 15:04:40 +01:00
dependabot[bot]
bcd798a057
Bump @typescript-eslint/eslint-plugin from 5.51.0 to 5.52.0 (#147) 2023-02-17 18:13:07 +01:00
dependabot[bot]
9faea4df2c
Bump eslint from 8.33.0 to 8.34.0 (#145) 2023-02-17 18:01:33 +01:00
dependabot[bot]
d6ca56b6e5
Bump actions/cache from 3.2.4 to 3.2.5 (#146) 2023-02-17 18:01:25 +01:00
dependabot[bot]
ab9c4fb17d
Bump @typescript-eslint/parser from 5.51.0 to 5.52.0 (#148) 2023-02-17 18:01:11 +01:00
dependabot[bot]
00ccd46018
Bump jest from 29.4.2 to 29.4.3 (#150) 2023-02-17 18:01:00 +01:00
dependabot[bot]
705c7f4f4f
Bump @jest/globals from 29.4.2 to 29.4.3 (#149) 2023-02-17 18:00:52 +01:00
dependabot[bot]
29801d3f81
Bump json-schema-to-zod from 0.6.2 to 0.6.3 (#151) 2023-02-17 18:00:40 +01:00
dependabot[bot]
8e71014357
Bump cacheable-request from 10.2.2 to 10.2.7 (#144) 2023-02-11 09:28:07 +01:00
dependabot[bot]
e086844f3f
Bump fastify from 4.12.0 to 4.13.0 (#143) 2023-02-10 07:38:46 +01:00
dependabot[bot]
80c64cf0e3
Bump zod from 3.20.5 to 3.20.6 (#142) 2023-02-10 07:38:31 +01:00
dependabot[bot]
3b15f7f37a
Bump zod from 3.20.2 to 3.20.5 (#141) 2023-02-09 09:34:42 +01:00
dependabot[bot]
ac8caabcda
Bump prettier from 2.8.3 to 2.8.4 (#140) 2023-02-09 09:34:28 +01:00
dependabot[bot]
cb415fa855
Bump @types/node from 18.11.19 to 18.13.0 (#139) 2023-02-08 09:13:22 +01:00
dependabot[bot]
001aaf069a
Bump jest from 29.4.1 to 29.4.2 (#138) 2023-02-08 09:13:15 +01:00
dependabot[bot]
1ff3300a85
Bump @jest/globals from 29.4.1 to 29.4.2 (#137) 2023-02-08 09:13:05 +01:00
dependabot[bot]
55b67bc1dd
Bump @types/node from 18.11.18 to 18.11.19 (#134) 2023-02-07 16:14:37 +01:00
dependabot[bot]
736cc88149
Bump @typescript-eslint/parser from 5.50.0 to 5.51.0 (#136) 2023-02-07 08:01:59 +01:00
dependabot[bot]
2f6f6c816c
Bump @typescript-eslint/eslint-plugin from 5.50.0 to 5.51.0 (#135) 2023-02-07 08:01:49 +01:00
dependabot[bot]
c85023b9d6
Bump http-cache-semantics from 4.1.0 to 4.1.1 (#133) 2023-02-02 13:19:57 +01:00
dependabot[bot]
0322126616
Bump @typescript-eslint/eslint-plugin from 5.49.0 to 5.50.0 (#132) 2023-02-02 13:19:47 +01:00
dependabot[bot]
6fe53182aa
Bump @typescript-eslint/parser from 5.49.0 to 5.50.0 (#131) 2023-02-02 09:22:44 +01:00
dependabot[bot]
a7a39a4a1c
Bump actions/cache from 3.2.3 to 3.2.4 (#129) 2023-01-31 09:34:40 +01:00
dependabot[bot]
9ab7f7f442
Bump typescript from 4.9.4 to 4.9.5 (#130) 2023-01-31 09:32:59 +01:00
dependabot[bot]
9e34c098cc
Bump eslint from 8.32.0 to 8.33.0 (#128) 2023-01-30 10:31:46 +01:00
dependabot[bot]
5f25da655a
Bump jest from 29.4.0 to 29.4.1 (#127) 2023-01-27 01:40:33 +01:00
dependabot[bot]
710f2b07e7
Bump @jest/globals from 29.4.0 to 29.4.1 (#126) 2023-01-27 01:40:23 +01:00
dependabot[bot]
93cf430c19
Bump jest from 29.3.1 to 29.4.0 (#124) 2023-01-25 10:54:29 +01:00
dependabot[bot]
41ae48102c
Bump rimraf from 4.1.1 to 4.1.2 (#125) 2023-01-25 10:54:15 +01:00
dependabot[bot]
e08596d098
Bump @jest/globals from 29.3.1 to 29.4.0 (#123) 2023-01-25 10:54:01 +01:00
dependabot[bot]
5166af0d25
Bump cookiejar from 2.1.3 to 2.1.4 (#122) 2023-01-24 10:28:58 +01:00
dependabot[bot]
e9c4c8fb29
Bump @typescript-eslint/parser from 5.48.2 to 5.49.0 (#121) 2023-01-24 09:59:02 +01:00
dependabot[bot]
540fad5757
Bump @typescript-eslint/eslint-plugin from 5.48.2 to 5.49.0 (#120) 2023-01-24 09:58:40 +01:00
dependabot[bot]
891ae81a2c
Bump fastify from 4.11.0 to 4.12.0 (#119) 2023-01-23 11:01:36 +01:00
dependabot[bot]
4af4035ce9
Bump rimraf from 4.0.7 to 4.1.1 (#118) 2023-01-18 08:29:39 +01:00
dependabot[bot]
9213eb707b
Bump @typescript-eslint/eslint-plugin from 5.48.1 to 5.48.2 (#117) 2023-01-17 10:17:26 +01:00
dependabot[bot]
a794d7bf39
Bump eslint-plugin-import from 2.27.4 to 2.27.5 (#116) 2023-01-17 09:31:36 +01:00
dependabot[bot]
0df74d8ff0
Bump @typescript-eslint/parser from 5.48.1 to 5.48.2 (#115) 2023-01-17 09:31:04 +01:00
dependabot[bot]
e480235537
Bump eslint from 8.31.0 to 8.32.0 (#111) 2023-01-16 04:16:57 +01:00
dependabot[bot]
484280f497
Bump prettier from 2.8.2 to 2.8.3 (#113) 2023-01-16 04:16:34 +01:00
dependabot[bot]
5707c03cd4
Bump ts-jest from 29.0.4 to 29.0.5 (#114) 2023-01-16 04:16:04 +01:00
dependabot[bot]
4b7d9ee94e
Bump rimraf from 4.0.1 to 4.0.7 (#112) 2023-01-16 04:15:03 +01:00
dependabot[bot]
eddc283983
Bump eslint-plugin-import from 2.27.0 to 2.27.4 (#110) 2023-01-13 10:49:41 +01:00
dependabot[bot]
438ec423c4
Bump rimraf from 3.0.2 to 4.0.1 (#109) 2023-01-13 10:49:05 +01:00
Lars Strojny
a8cc8290e2
Fix build 2023-01-12 16:15:30 +01:00
dependabot[bot]
d70eb9064a
Bump eslint-plugin-import from 2.26.0 to 2.27.0 (#108) 2023-01-12 13:00:58 +01:00
dependabot[bot]
06d74c57ce
Bump hap-node-client from 0.2.2 to 0.2.4 (#105) 2023-01-12 13:00:42 +01:00
dependabot[bot]
e99c4c57a2
Bump eslint-import-resolver-typescript from 3.5.2 to 3.5.3 (#106) 2023-01-12 12:56:54 +01:00
dependabot[bot]
744cd7ed4c
Bump ts-jest from 29.0.3 to 29.0.4 (#107) 2023-01-12 12:56:24 +01:00
dependabot[bot]
cb8d04a2b2
Bump @typescript-eslint/eslint-plugin from 5.48.0 to 5.48.1 (#104) 2023-01-10 11:46:44 +01:00
dependabot[bot]
317b8b598b
Bump @typescript-eslint/parser from 5.48.0 to 5.48.1 (#103) 2023-01-10 10:49:10 +01:00
dependabot[bot]
c9c9ba45eb
Bump actions/cache from 3.2.2 to 3.2.3 (#102) 2023-01-10 10:48:58 +01:00
dependabot[bot]
fe524c1f0a
Bump prettier from 2.8.1 to 2.8.2 (#100) 2023-01-09 02:36:49 +01:00
dependabot[bot]
bbc21f4c6e
Bump hap-node-client from 0.2.1 to 0.2.2 (#98) 2023-01-09 02:36:39 +01:00
dependabot[bot]
bd17d79c0d
Bump json-schema-to-zod from 0.6.1 to 0.6.2 (#99) 2023-01-09 02:36:29 +01:00
dependabot[bot]
de6ff2decd
Bump actions/upload-artifact from 3.1.1 to 3.1.2 (#101) 2023-01-09 02:36:03 +01:00
Lars Strojny
76a3ad9c7f
Release 1.0.5 2023-01-08 17:22:39 +01:00
Lars Strojny
33a7c9d0cf
Terminate output with a linefeed 2023-01-08 17:21:41 +01:00
Lars Strojny
8935a2402c
Release 1.0.4 2023-01-08 15:13:32 +01:00
Lars Strojny
c5f289946c
Fix missing metrics grouping (#97)
Closes #96
2023-01-08 15:12:01 +01:00
dependabot[bot]
be7210d375
Bump actions/checkout from 3.2.0 to 3.3.0 (#95) 2023-01-06 10:54:05 +01:00
dependabot[bot]
70468821ea
Bump actions/setup-node from 3.5.1 to 3.6.0 (#94) 2023-01-06 10:53:34 +01:00
dependabot[bot]
cc19c0fe40
Bump hap-node-client from 0.1.29 to 0.2.1 (#93) 2023-01-04 11:59:40 +01:00
dependabot[bot]
b4a263e4e4
Bump json5 from 1.0.1 to 1.0.2 (#92) 2023-01-03 22:22:57 +01:00
Lars Strojny
f1746e47b3
Release 1.0.3 2023-01-03 22:04:58 +01:00
Lars Strojny
3c9ecc1f65
Skip to 1.0.2 2023-01-03 22:03:48 +01:00
Lars Strojny
67824f6a37
Document configuration default values (#91) 2023-01-03 22:00:31 +01:00
dependabot[bot]
626ff1951b
Bump @typescript-eslint/parser from 5.47.1 to 5.48.0 (#90) 2023-01-03 11:52:44 +01:00
dependabot[bot]
3f405e1a70
Bump @typescript-eslint/eslint-plugin from 5.47.1 to 5.48.0 (#89) 2023-01-03 11:52:08 +01:00
dependabot[bot]
0b98217d3b
Bump json-schema-to-zod from 0.6.0 to 0.6.1 (#86) 2023-01-02 13:45:18 +01:00
dependabot[bot]
8d16187a0a
Bump eslint from 8.30.0 to 8.31.0 (#87) 2023-01-02 13:45:09 +01:00
dependabot[bot]
a850d1cbdf
Bump fastify from 4.10.2 to 4.11.0 (#88) 2023-01-02 13:44:59 +01:00
dependabot[bot]
13d3867522
Bump release-it from 15.5.1 to 15.6.0 (#84) 2022-12-31 19:18:53 +01:00
dependabot[bot]
23a891be36
Bump actions/cache from 3.2.1 to 3.2.2 (#85) 2022-12-31 19:18:44 +01:00
dependabot[bot]
2acb8da732
Bump @typescript-eslint/parser from 5.47.0 to 5.47.1 (#82) 2022-12-27 14:07:35 +01:00
dependabot[bot]
92070192d8
Bump actions/cache from 3.2.0 to 3.2.1 (#80) 2022-12-27 14:07:21 +01:00
dependabot[bot]
3e9803c106
Bump @types/node from 18.11.17 to 18.11.18 (#81) 2022-12-27 14:03:43 +01:00
dependabot[bot]
e780b319c7
Bump @typescript-eslint/eslint-plugin from 5.47.0 to 5.47.1 (#83) 2022-12-27 14:03:31 +01:00
dependabot[bot]
f971f044b6
Bump actions/cache from 3.0.11 to 3.2.0 (#78) 2022-12-22 09:37:12 +01:00
dependabot[bot]
abba84e44b
Bump hap-node-client from 0.1.28 to 0.1.29 (#77) 2022-12-21 14:01:25 +01:00
Lars Strojny
ed69818d61
Update package.lock 2022-12-20 13:57:41 +01:00
Lars Strojny
64b180c059
Replace npx with portable npx exec (#76)
`npx` will also consider global installations potentially leading to
build issues.
2022-12-20 13:57:00 +01:00
dependabot[bot]
482aa769a1
Bump @typescript-eslint/parser from 5.46.1 to 5.47.0 (#75) 2022-12-20 10:58:08 +01:00
dependabot[bot]
f8d4e43aef
Bump @typescript-eslint/eslint-plugin from 5.46.1 to 5.47.0 (#74) 2022-12-20 10:57:35 +01:00
dependabot[bot]
82506f8b45
Bump @types/node from 18.11.15 to 18.11.17 (#73) 2022-12-19 09:23:55 +01:00
dependabot[bot]
b31833ad61
Bump eslint from 8.29.0 to 8.30.0 (#72) 2022-12-19 09:23:46 +01:00
dependabot[bot]
62f3bea6cb
Bump hap-node-client from 0.1.25 to 0.1.28 (#71) 2022-12-19 09:23:28 +01:00
Lars Strojny
92ed6b139f
Release 1.0.1 2022-12-18 21:29:27 +01:00
Lars Strojny
0c96be8fbe
Fixate runner OS version 2022-12-18 21:26:58 +01:00
dependabot[bot]
df95319003
Bump actions/setup-node from 3.5.0 to 3.5.1 (#70) 2022-12-18 21:20:26 +01:00
Lars Strojny
c4ce1531cc
Specify exact version for GH actions to avoid accidental breakage (#69)
To avoid accidental breakage when an action is implicitly upgraded,
let’s fixate the action versions and let dependabot do it’s thing.
Therefore when an action upgrade breaks the build it becomes visible in
the dependabot PR.
2022-12-18 21:17:18 +01:00
Lars Strojny
78d45e770e
NPM v9 compatibility (#68)
`npm bin` no longer exists in npm version 9, so we use `npx` to run
local commands instead. `npm exec` would work as well, but that was only
introduced with npx version 7 and node 14 uses version 6. Duh!
2022-12-18 21:01:04 +01:00
dependabot[bot]
774a36e38b
Bump @types/node from 18.11.13 to 18.11.15 (#67) 2022-12-14 13:01:34 +01:00
Lars Strojny
936301322d
Fix flake.nix description 2022-12-14 00:24:14 +01:00
dependabot[bot]
57cfe61290
Bump @typescript-eslint/eslint-plugin from 5.46.0 to 5.46.1 (#64) 2022-12-13 13:38:01 +01:00
Lars Strojny
5907f57339
Turn off import/no-named-as-default rule 2022-12-13 12:14:56 +01:00
dependabot[bot]
5243d8c68c
Bump @typescript-eslint/parser from 5.46.0 to 5.46.1 (#63) 2022-12-13 12:04:25 +01:00
dependabot[bot]
3657968ce3
Bump zod from 3.19.1 to 3.20.2 (#65) 2022-12-13 12:04:12 +01:00
dependabot[bot]
b04326d378
Bump @types/node from 18.11.12 to 18.11.13 (#62) 2022-12-12 08:29:46 +01:00
Lars Strojny
5a8f056ef7
Release 1.0.0 2022-12-09 22:32:27 +01:00
Lars Strojny
a1aa1d775b
Switch back to json-schema-to-zod mainline (#60)
After https://github.com/StefanTerdell/json-schema-to-zod/pull/18 and
https://github.com/StefanTerdell/json-schema-to-zod/pull/24 got merged,
let’s switch back to the mainline.
2022-12-09 22:30:27 +01:00
dependabot[bot]
5e5a9acf9f
Bump @typescript-eslint/parser from 5.45.1 to 5.46.0 (#57) 2022-12-09 22:25:10 +01:00
dependabot[bot]
7429e433cd
Bump @typescript-eslint/eslint-plugin from 5.45.1 to 5.46.0 (#58) 2022-12-09 22:24:59 +01:00
dependabot[bot]
a8cc0f24c2
Bump @types/node from 18.11.11 to 18.11.12 (#59) 2022-12-09 22:24:49 +01:00
dependabot[bot]
b796e657de
Bump typescript from 4.9.3 to 4.9.4 (#56) 2022-12-08 09:25:03 +01:00
dependabot[bot]
41afec3769
Bump prettier from 2.8.0 to 2.8.1 (#55) 2022-12-08 09:24:51 +01:00
dependabot[bot]
790e9774a2
Bump supertest from 6.3.2 to 6.3.3 (#54) 2022-12-08 09:24:39 +01:00
dependabot[bot]
a50c464a0f
Bump @typescript-eslint/parser from 5.45.0 to 5.45.1 (#52) 2022-12-07 12:11:33 +01:00
dependabot[bot]
df552b6f3f
Bump @types/node from 18.11.10 to 18.11.11 (#53) 2022-12-06 10:30:11 +01:00
dependabot[bot]
9dd0b270ec
Bump @typescript-eslint/eslint-plugin from 5.45.0 to 5.45.1 (#51) 2022-12-06 10:29:22 +01:00
dependabot[bot]
ec77f03f5e
Bump release-it from 15.5.0 to 15.5.1 (#48) 2022-12-05 10:48:22 +01:00
dependabot[bot]
4d79aaee71
Bump supertest from 6.3.1 to 6.3.2 (#50) 2022-12-05 10:47:41 +01:00
dependabot[bot]
6bc63894e6
Bump eslint from 8.28.0 to 8.29.0 (#49) 2022-12-05 10:47:30 +01:00
dependabot[bot]
0d1dd28b8b
Bump @types/node from 18.11.9 to 18.11.10 (#47) 2022-12-03 18:34:15 +01:00
Lars Strojny
13493b1eb2
Release 0.1.0 2022-12-01 01:41:01 +01:00
dependabot[bot]
70e0d06c95
Bump homebridge from 1.5.1 to 1.6.0 (#42)
Bumps [homebridge](https://github.com/homebridge/homebridge) from 1.5.1
to 1.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/homebridge/homebridge/releases">homebridge's
releases</a>.</em></p>
<blockquote>
<h2>v1.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Updated <code>hap-nodejs</code> to <a
href="https://github.com/homebridge/HAP-NodeJS/releases/tag/v0.11.0">v0.11.0</a>
adding support for <code>systemd-resolved</code> mDNS advertisers and
improved support for systems running avahi mDNS advertiser. The release
also contains general bug fixes and improvements.</li>
<li>Support resolved mDNS advertiser by <a
href="https://github.com/elyscape"><code>@​elyscape</code></a> in <a
href="https://github-redirect.dependabot.com/homebridge/homebridge/pull/3260">homebridge/homebridge#3260</a></li>
<li>Update to provide compatibility with hap-nodejs 0.11.0 by <a
href="https://github.com/Supereg"><code>@​Supereg</code></a> in <a
href="https://github-redirect.dependabot.com/homebridge/homebridge/pull/3263">homebridge/homebridge#3263</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/elyscape"><code>@​elyscape</code></a>
made their first contribution in <a
href="https://github-redirect.dependabot.com/homebridge/homebridge/pull/3260">homebridge/homebridge#3260</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/homebridge/homebridge/compare/v1.5.1...v1.6.0">https://github.com/homebridge/homebridge/compare/v1.5.1...v1.6.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d98b430f69"><code>d98b430</code></a>
1.6.0</li>
<li><a
href="87452ceb6f"><code>87452ce</code></a>
Switch to hap-nodejs v0.11.0 release channel</li>
<li><a
href="708c718d71"><code>708c718</code></a>
Update to provide compatibility with hap-nodejs 0.11.0 (<a
href="https://github-redirect.dependabot.com/homebridge/homebridge/issues/3263">#3263</a>)</li>
<li><a
href="3d281f4fd3"><code>3d281f4</code></a>
Workaround hap-nodejs beta issues</li>
<li><a
href="7b764798e4"><code>7b76479</code></a>
Bump hap-nodejs beta channel</li>
<li><a
href="4bcf8829bd"><code>4bcf882</code></a>
Support resolved mDNS advertiser (<a
href="https://github-redirect.dependabot.com/homebridge/homebridge/issues/3260">#3260</a>)</li>
<li><a
href="6c2f927b20"><code>6c2f927</code></a>
Upgrade to hap-nodejs 0.11.0 beta channel</li>
<li>See full diff in <a
href="https://github.com/homebridge/homebridge/compare/v1.5.1...v1.6.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=homebridge&package-manager=npm_and_yarn&previous-version=1.5.1&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lars Strojny <lars@strojny.net>
2022-12-01 01:38:39 +01:00
dependabot[bot]
a556c3aca1
Bump @fastify/basic-auth from 4.0.0 to 5.0.0 (#46) 2022-12-01 00:48:38 +01:00
dependabot[bot]
9b41a89059
Bump @typescript-eslint/parser from 5.44.0 to 5.45.0 (#45) 2022-11-29 15:33:24 +01:00
dependabot[bot]
4fd8e9bdb1
Bump @fastify/auth from 4.1.0 to 4.2.0 (#44) 2022-11-29 15:33:12 +01:00
dependabot[bot]
861572d9c2
Bump @typescript-eslint/eslint-plugin from 5.44.0 to 5.45.0 (#43) 2022-11-29 15:32:59 +01:00
Lars Strojny
66a465d351
Exclude a few files from coverage reporting 2022-11-24 23:18:03 +01:00
Lars Strojny
19131ee9c3
Winter cleanup (#41)
Various small cleanups, code reorgs, etc.
2022-11-24 22:12:40 +01:00
dependabot[bot]
64d6dfe960
Bump @typescript-eslint/parser from 5.43.0 to 5.44.0 (#40) 2022-11-24 20:42:06 +01:00
Lars Strojny
a3ac5edee8
Configure dependabot for github actions 2022-11-24 20:40:44 +01:00
Lars Strojny
c7967e2200
Daily dependabot with more lenient limits 2022-11-24 20:40:03 +01:00
dependabot[bot]
5c59fa3816
Bump prettier from 2.7.1 to 2.8.0 (#39) 2022-11-24 20:37:32 +01:00
dependabot[bot]
7e52448bfe
Bump @typescript-eslint/eslint-plugin from 5.43.0 to 5.44.0 (#38) 2022-11-24 20:37:22 +01:00
Lars Strojny
f36112ff9c
Release 0.0.15 2022-11-24 20:26:14 +01:00
Lars Strojny
8a0061428d
Move conditions to the top 2022-11-24 20:21:10 +01:00
Lars Strojny
f30dfe0cee
Use constant instead of magic number 2022-11-24 20:18:48 +01:00
Lars Strojny
e5b3a90fae
Use string concatenation instead 2022-11-24 19:59:03 +01:00
Lars Strojny
d25207923c
Fix symbol name conflict 2022-11-24 19:58:07 +01:00
Lars Strojny
f45df5d422
Clean up prefix handling 2022-11-24 19:53:03 +01:00
Lars Strojny
65304798de
Memoized renderering (#37)
Pre-render metrics on discovery once and then reuse rendered response.
2022-11-24 19:35:02 +01:00
Lars Strojny
565f423dd9
Extract stringReverse() function 2022-11-24 19:24:00 +01:00
Lars Strojny
ccca32d7b2
Exclude dist folder from tests search paths 2022-11-24 19:23:40 +01:00
Lars Strojny
fa687f635f
Add quality gate status 2022-11-23 15:05:20 +01:00
dependabot[bot]
3282a084a4
Bump fastify from 4.10.0 to 4.10.2 (#35) 2022-11-23 15:03:46 +01:00
Lars Strojny
df81a1206a
Rename workflow and input workflow 2022-11-23 14:59:22 +01:00
Lars Strojny
1d3452cdb0
Separate SonarCloud step to safely run it on PRs 2022-11-23 14:53:13 +01:00
Lars Strojny
7c7673f1c2
Fix code smells (#36)
Fix code smells detected by SonarCloud
2022-11-23 14:30:23 +01:00
Lars Strojny
97de2f0905
Set up SonarCloud static analysis 2022-11-23 11:42:44 +01:00
Lars Strojny
ddc85f5359 Release 0.0.14 2022-11-21 03:08:04 +01:00
Lars Strojny
600a5bae61 Remove jq 2022-11-21 03:06:10 +01:00
Lars Strojny
b7364927df Add a few interesting badges 2022-11-21 03:00:34 +01:00
dependabot[bot]
cd08a3ecf6
Bump eslint from 8.27.0 to 8.28.0 (#33) 2022-11-21 02:49:01 +01:00
Lars Strojny
8f2d80a7fb
Unpin and upgrade jest-mock (#32)
Previously failed because of a type error deep in node_modules, let’s
try again.
2022-11-18 13:23:37 +01:00
dependabot[bot]
f70de293a5
Bump hap-nodejs from 0.10.4 to 0.11.0 (#31) 2022-11-18 13:11:43 +01:00
dependabot[bot]
e791b690aa
Bump typescript from 4.8.4 to 4.9.3 (#30) 2022-11-18 12:41:55 +01:00
dependabot[bot]
cc81f5868e
Bump @typescript-eslint/parser from 5.42.1 to 5.43.0 (#28) 2022-11-18 09:46:07 +01:00
dependabot[bot]
3e5006dd5d
Bump @typescript-eslint/eslint-plugin from 5.42.1 to 5.43.0 (#29) 2022-11-18 09:45:53 +01:00
dependabot[bot]
c24a9731c1
Bump fastify from 4.9.2 to 4.10.0 (#27) 2022-11-18 09:43:00 +01:00
Lars Strojny
fc1bf8b334 Release 0.0.13 2022-11-17 13:20:16 +01:00
Lars Strojny
6f7686ba34
Auto-generate configuration documentation in README (#26)
Auto-generate the documentation of the configuration options in
`README.md` from `config.schema.json`.
2022-11-17 13:19:22 +01:00
Lars Strojny
261fa74ac9
Add configuration option for the metric server interface (#25)
Add a configuration option `interface` to allow configuring where the
HTTP metric server should bind to.
2022-11-17 13:15:07 +01:00
Lars Strojny
04706f69d5 Release 0.0.12 2022-11-16 23:29:53 +01:00
Lars Strojny
2e8f2d0579 Clean build on release 2022-11-16 23:28:48 +01:00
Lars Strojny
9127408d23 Ignore release script 2022-11-16 23:26:32 +01:00
Lars Strojny
b8a4c2c9e6 Release 0.0.11 2022-11-16 22:50:48 +01:00
Lars Strojny
ce8876793d
Fall back to IPv4 if EAFNOSUPPORT (#24)
If listening to `::` fails, try `0.0.0.0` instead. Addresses #22
2022-11-16 22:47:41 +01:00
Lars Strojny
f8007b55ca
Add experimental support for basic auth and TLS (#23)
Allows restricting access to the monitoring endpoint using basic auth
and configure TLS certificates.
2022-11-16 22:19:08 +01:00
Lars Strojny
edf4e605e5
Cache expensive CI steps (#21)
Speed-up pipeline by caching eslint, prettier, TypeScript, node_modules
2022-11-14 02:43:27 +01:00
Lars Strojny
57e8a4d831 Reverse lint/tests exclusion on CI, better job name labels 2022-11-14 02:02:07 +01:00
Lars Strojny
479fbe5727 Refactor internal APIs 2022-11-14 01:20:53 +01:00
Lars Strojny
4fad222b82 Format insecure.conf path in README 2022-11-13 13:40:10 +01:00
Lars Strojny
34be5bbca2 Implement consistent request logging 2022-11-13 13:34:33 +01:00
Lars Strojny
8102144b85 Explicit type imports 2022-11-13 13:34:09 +01:00
Lars Strojny
83f64d3999 Release 0.0.10 2022-11-13 11:14:15 +01:00
Lars Strojny
b74721ee76
Handle null values in characteristics (#20)
Apparently `characteristic.value` can also be null. Let’s handle it
properly. Fixes #19
2022-11-13 11:13:13 +01:00
Lars Strojny
249e4cf10c Ignore local release script 2022-11-10 13:54:12 +01:00
Lars Strojny
1828c4836a Release 0.0.9 2022-11-10 13:46:23 +01:00
Lars Strojny
fd113c1868 Run npm test in CI mode for releases 2022-11-10 13:43:23 +01:00
Lars Strojny
ab90f03264
Set up release-it as the release management tool (#18)
Will manage the release and automatically generate proper releases on
GitHub
2022-11-10 13:40:34 +01:00
Lars Strojny
52efa69bf0
Automate code generation for config schema (#16)
Use a fork of https://github.com/lstrojny/json-schema-to-zod to generate
the boundary check for the config automatically.
2022-11-10 13:06:16 +01:00
Lars Strojny
1088a78079
Strict type imports (#17)
When a symbols is only used as a type, require `import type {…}`
2022-11-10 13:00:45 +01:00
Lars Strojny
58d2683e38 0.0.8 2022-11-10 11:45:37 +01:00
Lars Strojny
822f0da2eb
Set charset in metrics content type (#15)
Send `Content-Type: text/plain; charset=utf-8; version=0.0.4` instead of
`Content-Type: text/plain; version=0.0.4` so that special characters in
names have a chance to work.
2022-11-10 11:10:31 +01:00
Lars Strojny
f6bc8ca90c
Use code generation for UUID service map (#14)
Instead of introspecting *hap-nodejs* at runtime for UUID to service
mapping, generate the map during build time.
2022-11-10 11:07:31 +01:00
Lars Strojny
38bf24df90 0.0.7 2022-11-09 21:44:24 +01:00
Lars Strojny
828ae9eade Improve release management script 2022-11-09 21:40:34 +01:00
Lars Strojny
71e45a3d8c
More user-friendly (and developer-friendly) zod error reporting (#12)
Show each error message from zod and include an excerpt of the data
around the failing path if possible
2022-11-09 21:36:23 +01:00
Lars Strojny
0735327b7c 0.0.6 2022-11-09 19:22:44 +01:00
Lars Strojny
ad2715699f
Add funding info 2022-11-09 19:09:43 +01:00
Lars Strojny
2bd799ca9c
Integration tests for the fastify HTTP adapter (#11) 2022-11-09 19:08:24 +01:00
Lars Strojny
e1b5303384 Release management 2022-11-09 18:23:18 +01:00
48 changed files with 10965 additions and 2971 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

View file

@ -19,6 +19,8 @@ module.exports = {
'import/no-default-export': 'error',
'import/no-namespace': 'error',
'import/no-useless-path-segments': 'error',
'import/no-named-as-default': 0,
'no-duplicate-imports': 'error',
},
overrides: [
{
@ -33,6 +35,7 @@ module.exports = {
},
rules: {
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
},
settings: {
'import/resolver': {
@ -43,7 +46,7 @@ module.exports = {
},
},
{
files: ['.eslintrc.js', 'jest.config.js', 'prettier.config.js'],
files: ['**/*.js'],
env: {
node: true,
browser: false,

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: lstrojny
custom: ["https://paypal.me/larsstrojny"]

View file

@ -3,4 +3,11 @@ updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
interval: daily
open-pull-requests-limit: 30
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
open-pull-requests-limit: 30

View file

@ -4,40 +4,83 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
# the Node.js versions to build on
node-version: [14.x, 16.x, 17.x, 18.x]
allow-lint-failure: [false]
allow-test-failure: [false]
include:
- { node-version: 10.x, allow-lint-failure: true, allow-test-failure: true }
- { node-version: 11.x, allow-lint-failure: true, allow-test-failure: true }
- { node-version: 12.x, allow-lint-failure: true, allow-test-failure: true }
- { node-version: 13.x, allow-lint-failure: true, allow-test-failure: true }
- { node-version: 15.x, allow-lint-failure: true, allow-test-failure: false }
- { node-version: 14.x, lint: true, tests: true }
- { node-version: 15.x, lint: false, tests: true }
- { node-version: 16.x, lint: true, tests: true }
- { node-version: 17.x, lint: true, tests: true }
- { node-version: 18.x, lint: true, tests: true }
- { node-version: 19.x, lint: true, tests: true }
name: nodejs ${{ matrix.node-version }} (${{ matrix.lint && 'lint → ' || '' }}${{ matrix.tests && 'test → ' || '' }}build)
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v3.8.1
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
id: cache-npm
uses: actions/cache@v3.3.2
env:
cache-name: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/package.json') }}
- name: Cache eslint
id: cache-eslint
uses: actions/cache@v3.3.2
env:
cache-name: cache-eslint
with:
path: .eslintcache
key: ${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/package.json') }}
- name: Cache TypeScript
id: cache-typescript
uses: actions/cache@v3.3.2
env:
cache-name: cache-typescript
with:
path: .tsbuildinfo
key: ${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/package.json') }}
- name: Cache prettier
id: cache-prettier
uses: actions/cache@v3.3.2
env:
cache-name: cache-prettier
with:
path: node_modules/.cache/prettier/.prettier-cache
key: ${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/package.json') }}
- name: Install dependencies
run: npm install
if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
- name: Lint the project
run: npm run lint
continue-on-error: ${{ matrix.allow-lint-failure }}
if: ${{ matrix.lint }}
- name: Run tests
run: npm test
continue-on-error: ${{ matrix.allow-test-failure }}
if: ${{ matrix.tests }}
- name: Upload code coverage
uses: actions/upload-artifact@v3.1.3
with:
name: code-coverage
path: coverage/lcov.info
if: ${{ matrix.node-version == '18.x' }}
- name: Build the project
run: npm run build

View file

@ -0,0 +1,28 @@
name: Dependabot auto merge
on:
workflow_run:
workflows: [CI]
types:
- completed
jobs:
automerge:
name: Auto merge "${{ github.event.workflow_run.head_branch }}"
runs-on: ubuntu-22.04
if: >
github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'
&& github.actor == 'dependabot[bot]'
&& startsWith(github.event.workflow_run.head_branch, 'dependabot/')
steps:
- name: Checkout source
uses: actions/checkout@v4.1.0
with:
ref: ${{ github.event.workflow_run.head_commit.id }}
- name: Instruct @dependabot to merge
run: "gh issue comment $ISSUE_ID --body \"(This is an automated comment from workflow $WORKFLOW_URL)\n\n@dependabot squash and merge\""
env:
GITHUB_TOKEN: ${{ secrets.DEPENDABOT_COMMENT_TOKEN }}
ISSUE_ID: ${{ github.event.workflow_run.pull_requests[0].number }}
WORKFLOW_URL: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}

55
.github/workflows/sonar.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Sonar scan
on:
workflow_run:
workflows: [CI]
types: [completed]
jobs:
sonar:
name: Sonar scan on "${{ github.event.workflow_run.head_branch }}"
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@v4.1.0
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_branch }}
fetch-depth: 0
- name: 'Download code coverage'
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "code-coverage"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/code-coverage.zip`, Buffer.from(download.data));
- name: 'Unzip code coverage'
run: unzip code-coverage.zip -d coverage
- name: SonarCloud scan
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
-Dsonar.pullrequest.key=${{ github.event.workflow_run.pull_requests[0].number }}
-Dsonar.pullrequest.branch=${{ github.event.workflow_run.pull_requests[0].head.ref }}
-Dsonar.pullrequest.base=${{ github.event.workflow_run.pull_requests[0].base.ref }}

2
.gitignore vendored
View file

@ -125,3 +125,5 @@ dist
/.homebridge/accessories/
/.homebridge/persist/
release.sh

View file

@ -142,3 +142,5 @@ prettier.config.js
.eslintrc.js
jest.config.js
nodemon.json
/code-generation/
/release.sh

9
.release-it.json Normal file
View file

@ -0,0 +1,9 @@
{
"github": {
"release": true
},
"hooks": {
"before:init": ["test `git rev-parse --abbrev-ref HEAD` == 'develop'", "npm run lint", "CI=1 npm test"],
"after:bump": "npm run build"
}
}

0
CHANGELOG.md Normal file
View file

102
README.md
View file

@ -11,7 +11,8 @@
width="10%"/>
</div>
# Homebridge Prometheus Exporter [![CI](https://github.com/lstrojny/homebridge-prometheus-exporter/actions/workflows/build.yml/badge.svg)](https://github.com/lstrojny/homebridge-prometheus-exporter/actions/workflows/build.yml)
# Homebridge Prometheus Exporter
[![CI](https://github.com/lstrojny/homebridge-prometheus-exporter/actions/workflows/build.yml/badge.svg)](https://github.com/lstrojny/homebridge-prometheus-exporter/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=lstrojny_homebridge-prometheus-exporter&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=lstrojny_homebridge-prometheus-exporter) [![npm version](https://badge.fury.io/js/homebridge-prometheus-exporter.svg)](https://badge.fury.io/js/homebridge-prometheus-exporter) ![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/lstrojny/homebridge-prometheus-exporter) ![npm](https://img.shields.io/npm/dw/homebridge-prometheus-exporter)
> What if we could store homebridge metrics in Prometheus
@ -63,7 +64,7 @@ Create `/etc/systemd/system/homebridge.service.d` folder:
```shell
mkdir /etc/systemd/system/homebridge.service.d
```
Write this drop-in configuration file to /etc/systemd/system/homebridge.service.d/insecure.conf:
Write this drop-in configuration file to `/etc/systemd/system/homebridge.service.d/insecure.conf`:
```ini
[Service]
ExecStart=
@ -116,34 +117,95 @@ Once *Prometheus* is restarted, metrics with the `homebridge_` prefix should sta
*homebridge-prometheus-exporter* offers a few advanced settings to customize its behavior.
```json lines
<!-- AUTOGENERATED CONFIG DOCS BEGIN -->
```json5
{
//
// ...
"platforms": [
{
"platform": "PrometheusExporter",
// Homebridge PIN for service authentication. String of digits, format XXX-XX-XXX. Required
"pin": string,
// Toggle debug mode. Run homebridge with -D if you want to see the debug output. Default: false
"debug": boolean,
// Prefix for all metrics. Default: "homebridge"
"prefix": string,
// Pin
//
// Homebridge PIN for service authentication
"pin": "<string>",
// TCP port where the Prometheus metrics server listens. Default: 36123
"port": number,
// How frequently the services should be rediscovered (in seconds). Default: 60
"refresh_interval": number,
// Debug
//
// Default: false
"debug": "<boolean>",
// Timeout for the HTTP request that retrieves the homekit devices (in seconds). Default: 10
"request_timeout": number,
// Timeout for the service discovery (in seconds). Default: 20
"discovery_timeout": number,
},
// …
// Metrics prefix
//
// Default: "homebridge"
"prefix": "<string>",
// Metrics server port
//
// TCP port where the Prometheus metrics server listens
//
// Default: 36123
"port": "<integer>",
// Metrics server interface
//
// Interface where the Prometheus metrics server listens. Can be an IP, a
// hostname, "0.0.0.0" for all IPv4 interfaces, "::1" for all IPv6 interfaces.
// Default is "::" which means "any interface"
//
// Default: "::"
"interface": "<string>",
// Service refresh interval
//
// Discover new services every <interval> seconds
//
// Default: 60
"refresh_interval": "<integer>",
// Request timeout
//
// Request timeout when interacting with homebridge instances
//
// Default: 10
"request_timeout": "<integer>",
// Service discovery timeout
//
// Discovery timeout after which the current discovery is considered failed
//
// Default: 20
"discovery_timeout": "<integer>",
// TLS cert file
//
// Path to TLS certificate file (in PEM format)
"tls_cert_file": "<string>",
// TLS key file
//
// Path to TLS key file
"tls_key_file": "<string>",
// Basic auth username/password pairs
//
// Usernames and passwords for basic auth. Object key is the username, object
// value is the password. Password must be encoded with bcrypt. Example:
// {"joanna": "$2a$12$5/mmmRB28wg9yzaXhee5Iupq3UrFr/qMgAe9LvAxGoY5jLcfVGTUq"}
"basic_auth": "<object>"
}
]
}
```
<!-- AUTOGENERATED CONFIG DOCS END -->

View file

@ -0,0 +1,102 @@
#!/usr/bin/env node
const { parseSchema } = require('json-schema-to-zod')
const { schema } = require('../config.schema.json')
const { format } = require('prettier')
const { join, basename } = require('path')
const prettierConfig = require('../prettier.config')
const { writeFileSync, readFileSync } = require('fs')
const file = join(__dirname, '../src/generated/config_boundary.ts')
console.log(`Starting code generation for ${file}`)
const zodSchema = parseSchema(schema, false)
const code = format(
`
// Auto-generated by "${join(basename(__dirname), basename(__filename))}", dont manually edit
import { z } from 'zod'
export const ConfigBoundary = ${zodSchema}
`,
{ filepath: 'codegen.ts', ...prettierConfig },
)
writeFileSync(file, code)
const note = 'AUTOGENERATED CONFIG DOCS'
const comment = (...strings) => `<!-- ${strings.join(' ')} -->`
const readmePath = join(__dirname, '../README.md')
const readme = readFileSync(readmePath).toString()
const regex = new RegExp(`${comment(note, 'BEGIN')}.*${comment(note, 'END')}`, 'mgs')
if (!readme.match(regex)) {
console.log('Could not update README.md')
process.exit(1)
}
writeFileSync(
readmePath,
readme.replace(
regex,
`${comment(note, 'BEGIN')}\n\`\`\`json5\n${generateDocs(schema)}\n\`\`\`\n${comment(note, 'END')}`,
),
)
function generateDocs(schema) {
const doc = indent(
Object.entries(schema.properties)
.map(([property, definition]) => {
const lines = []
if (definition.title) {
lines.push(`// ${definition.title}`)
}
if (definition.description) {
lines.push(`//\n// ${wordwrap(definition.description, 80, '\n// ')}`)
}
if (definition.default !== undefined) {
lines.push(`//\n// Default: ${JSON.stringify(definition.default)}`)
}
lines.push(`${JSON.stringify(property)}: ${JSON.stringify('<' + definition.type + '>')}`)
return lines.join('\n')
})
.join(',\n\n\n'),
6,
)
return `{
// ...
"platforms": [
{
"platform": "PrometheusExporter",
${doc}
}
]
}`
}
function wordwrap(word, length, wrap = '\n') {
const wrapped = []
while (word.length > length) {
const cut = word.substring(0, length)
const pos = cut.lastIndexOf(' ')
wrapped.push(cut.substring(0, pos))
word = word.substring(pos + 1)
}
wrapped.push(word)
return wrapped.join(wrap)
}
function indent(string, indent) {
return string.replace(/^(.+)$/gm, `${' '.repeat(indent)}$1`)
}
console.log(`Finished code generation for ${file}`)

36
code-generation/hap-gen.js Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env node
const hap = require('hap-nodejs')
const { format } = require('prettier')
const prettierConfig = require('../prettier.config')
const { writeFileSync } = require('fs')
const { join, basename } = require('path')
const uuidToServiceMap = {}
const serviceToUuidMap = {}
const file = join(__dirname, '../src/generated/services.ts')
console.log(`Starting code generation for ${file}`)
for (const [name, service] of Object.entries(hap.Service)) {
if (typeof service !== 'function' || typeof service.UUID !== 'string') {
console.log(`Skipping ${typeof service} ${name}`)
continue
}
uuidToServiceMap[service.UUID] = name
serviceToUuidMap[name] = service.UUID
}
const code = format(
`
// Auto-generated by "${join(basename(__dirname), basename(__filename))}", dont manually edit
export const Uuids = ${JSON.stringify(uuidToServiceMap)} as const
export const Services = ${JSON.stringify(serviceToUuidMap)} as const
`,
{ filepath: 'codegen.ts', ...prettierConfig },
)
writeFileSync(file, code)
console.log(`Finished code generation for ${file}`)

View file

@ -26,12 +26,18 @@
"default": "homebridge"
},
"port": {
"title": "Probe server port",
"description": "TCP port for the prometheus probe server to listen to",
"title": "Metrics server port",
"description": "TCP port where the Prometheus metrics server listens",
"type": "integer",
"required": false,
"default": 36123
},
"interface": {
"title": "Metrics server interface",
"description": "Interface where the Prometheus metrics server listens. Can be an IP, a hostname, \"0.0.0.0\" for all IPv4 interfaces, \"::1\" for all IPv6 interfaces. Default is \"::\" which means \"any interface\"",
"type": "string",
"default": "::"
},
"refresh_interval": {
"title": "Service refresh interval",
"description": "Discover new services every <interval> seconds",
@ -52,6 +58,25 @@
"type": "integer",
"required": false,
"default": 20
},
"tls_cert_file": {
"title": "TLS cert file",
"description": "Path to TLS certificate file (in PEM format)",
"type": "string",
"required": false
},
"tls_key_file": {
"title": "TLS key file",
"description": "Path to TLS key file",
"type": "string",
"required": false
},
"basic_auth": {
"title": "Basic auth username/password pairs",
"description": "Usernames and passwords for basic auth. Object key is the username, object value is the password. Password must be encoded with bcrypt. Example: {\"joanna\": \"$2a$12$5/mmmRB28wg9yzaXhee5Iupq3UrFr/qMgAe9LvAxGoY5jLcfVGTUq\"}",
"type": "object",
"additionalProperties": { "type": "string" },
"required": false
}
}
}

View file

@ -1,5 +1,5 @@
{
description = "Ansible environment";
description = "homebridge-prometheus-exporter";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@ -16,6 +16,6 @@
in with pkgs; rec {
devShell = pkgs.mkShell rec { buildInputs = with pkgs; [ nodejs jq ]; };
devShell = pkgs.mkShell rec { buildInputs = with pkgs; [ nodejs ]; };
});
}

View file

@ -3,4 +3,5 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
verbose: true,
testPathIgnorePatterns: ['dist'],
}

12209
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "homebridge-prometheus-exporter",
"version": "0.0.5",
"version": "1.0.5",
"description": "Prometheus exporter for homebridge accessories.",
"license": "Apache-2.0",
"repository": {
@ -16,12 +16,15 @@
},
"main": "dist/src/index.js",
"scripts": {
"lint": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; `npm bin`/prettier --ignore-path=.gitignore `ifNotCi --write --check` '**/**.{ts,js,json}' && `npm bin`/eslint `ifNotCi --fix` --ignore-path=.gitignore '**/**.{ts,js,json}'",
"_portable_exec": "npmPortableExec() { `npm root`/.bin/$@; }; npmPortableExec",
"lint": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; npm run _portable_exec -- tsc --noEmit && npm run _portable_exec -- prettier --ignore-path=.gitignore `ifNotCi --write \"--check --cache --cache-strategy content\"` '**/**.{ts,js,json}' && npm run _portable_exec -- eslint `ifNotCi --fix \"--cache --cache-strategy content\"` --ignore-path=.gitignore '**/**.{ts,js,json}'",
"start": "npm run build && npm run link && nodemon",
"test": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; `npm bin`/jest `ifNotCi --watchAll`",
"test": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; npm run code-generation && npm run _portable_exec -- jest `ifNotCi --watchAll --collect-coverage`",
"link": "npm install --no-save file:///$PWD/",
"build": "rimraf ./dist && tsc",
"prepublishOnly": "npm run lint && npm run build"
"build": "rimraf ./dist .tsbuildinfo && npm run code-generation && tsc",
"code-generation": "./code-generation/hap-gen.js && ./code-generation/config-scheme-gen.js",
"prepublishOnly": "npm run code-generation && npm run lint && npm run build",
"release": "release-it --only-version"
},
"keywords": [
"homebridge-plugin",
@ -31,30 +34,37 @@
],
"devDependencies": {
"@jest/globals": "^29.3.0",
"@types/node": "^18.11.9",
"@types/bcrypt": "^5.0.0",
"@types/node": "^20.2.3",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"array.prototype.flatmap": "^1.3.1",
"eslint": "^8.0.1",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"hap-nodejs": "^0.11.0",
"homebridge": "^1.3.5",
"homebridge-cmdswitch2": "^0.2.10",
"jest": "^29.3.0",
"json-schema-to-zod": "^0.2.0",
"nodemon": "^2.0.13",
"json-schema-to-zod": "^0.6.0",
"nodemon": "^3.0.1",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"release-it": "^16.0.0",
"rimraf": "^5.0.0",
"supertest": "^6.3.1",
"ts-jest": "^29.0.3",
"ts-node": "^10.3.0",
"typescript": "^4.4.4"
"typescript": "^5.0.2"
},
"dependencies": {
"@fastify/auth": "^4.1.0",
"@fastify/basic-auth": "^5.0.0",
"array.prototype.group": "^1.1.2",
"bcrypt": "^5.1.0",
"fastify": "^4.9.2",
"hap-node-client": "^0.1.25",
"hap-node-client": "^0.2.1",
"zod": "^3.19.1"
},
"overrides": {
"jest-mock": "29.2"
}
}

7
sonar-project.properties Normal file
View file

@ -0,0 +1,7 @@
sonar.projectKey=lstrojny_homebridge-prometheus-exporter
sonar.organization=lstrojny
sonar.sources=.
sonar.exclusions=tests/**
sonar.tests=tests
sonar.javascript.lcov.reportPaths=./coverage/lcov.info
sonar.coverage.exclusions=code-generation/**,*.config.js,src/generated/**

View file

@ -1,14 +1,9 @@
import type { Device } from '../../boundaries/hap'
import { Logger } from 'homebridge'
import type { Config, Device } from '../../boundaries'
import type { Logger } from 'homebridge'
type Pin = string
export interface HapConfig {
pin: Pin
refreshInterval: number
discoveryTimeout: number
requestTimeout: number
logger: Logger
debug: boolean
export interface HapDiscoveryConfig {
config: Pick<Config, 'debug' | 'pin' | 'refresh_interval' | 'discovery_timeout' | 'request_timeout'>
log?: Logger
}
export type HapDiscover = (config: HapConfig) => Promise<Device[]>
export type HapDiscover = (config: HapDiscoveryConfig) => Promise<Device[]>

View file

@ -1,64 +1,66 @@
import type { HapDiscover } from './api'
import { HAPNodeJSClient } from 'hap-node-client'
import { Device, DeviceBoundary } from '../../boundaries/hap'
import { Logger } from 'homebridge'
import { HAPNodeJSClient, type HAPNodeJSClientConfig } from 'hap-node-client'
import { type Device, DeviceBoundary, checkBoundary } from '../../boundaries'
import type { Logger } from 'homebridge'
import z from 'zod'
const MaybeDevices = z.array(z.unknown())
interface HapConfig {
debug: boolean
refresh: number
timeout: number
reqTimeout: number
pin: string
}
type ResolveFunc = (devices: Device[]) => void
type RejectFunc = (error: unknown) => void
const clientMap: Record<string, HAPNodeJSClient> = {}
const promiseMap: Record<string, [ResolveFunc, RejectFunc]> = {}
function startDiscovery(logger: Logger, config: HapConfig, resolve: ResolveFunc, reject: RejectFunc) {
function startDiscovery(
logger: Logger | undefined,
config: HAPNodeJSClientConfig,
resolve: ResolveFunc,
reject: RejectFunc,
) {
const key = JSON.stringify(config)
if (!clientMap[key]) {
logger.debug('Creating new HAP client')
const client = new HAPNodeJSClient(config)
client.on('Ready', (deviceData: unknown) => {
try {
const devices: Device[] = []
for (const device of MaybeDevices.parse(deviceData)) {
try {
devices.push(DeviceBoundary.parse(device))
} catch (e) {
logger.error('Boundary check for device data failed %o %s', e, JSON.stringify(device, null, 4))
}
}
if (promiseMap[key]) promiseMap[key][0](devices)
} catch (e) {
if (promiseMap[key]) promiseMap[key][1](e)
}
})
clientMap[key] = client
} else {
logger.debug('Reusing existing HAP client')
}
promiseMap[key] = [resolve, reject]
if (!clientMap[key]) {
logger?.debug('Creating new HAP client')
clientMap[key] = new HAPNodeJSClient(config)
clientMap[key].on('Ready', createDiscoveryHandler(logger, key))
} else {
logger?.debug('Reusing existing HAP client')
}
}
export const discover: HapDiscover = ({ pin, refreshInterval, discoveryTimeout, requestTimeout, logger, debug }) => {
function createDiscoveryHandler(logger: Logger | undefined, key: string): (deviceData: unknown) => void {
return (deviceData: unknown) => {
try {
const devices: Device[] = []
for (const device of checkBoundary(MaybeDevices, deviceData)) {
try {
devices.push(checkBoundary(DeviceBoundary, device))
} catch (e) {
logger?.error('Boundary check for device data failed %o %s', e, JSON.stringify(device, null, 4))
}
}
if (promiseMap[key]) promiseMap[key][0](devices)
} catch (e) {
if (promiseMap[key]) promiseMap[key][1](e)
}
}
}
export const hapNodeJsClientDiscover: HapDiscover = ({ config, log }) => {
return new Promise((resolve, reject) => {
startDiscovery(
logger,
log,
{
debug: debug,
refresh: refreshInterval,
timeout: discoveryTimeout,
reqTimeout: requestTimeout,
pin,
debug: config.debug,
refresh: config.refresh_interval,
timeout: config.discovery_timeout,
reqTimeout: config.request_timeout,
pin: config.pin,
},
resolve,
reject,

View file

@ -0,0 +1,2 @@
export * from './api'
export * from './hap_node_js_client'

View file

@ -1,4 +1,7 @@
import { HttpServer } from '../../http'
import type { Logger } from 'homebridge'
import type { RequestListener, Server } from 'http'
import type { Config } from '../../boundaries'
import type { Metric } from '../../metrics'
export interface HttpResponse {
statusCode?: number
@ -11,3 +14,19 @@ export interface HttpServerController {
}
export type HttpAdapter = (config: HttpServer) => Promise<HttpServerController>
export type HttpConfig = Pick<
Config,
'debug' | 'port' | 'interface' | 'prefix' | 'basic_auth' | 'tls_cert_file' | 'tls_key_file'
>
export interface HttpServer {
log: Logger | null
config: HttpConfig
serverFactory?: (requestListener: RequestListener) => Server
onRequest(): HttpResponse | null
onMetrics(): HttpResponse
onNotFound(): HttpResponse
onError(error: Error): HttpResponse
onMetricsDiscovery(metrics: Metric[]): void
}

View file

@ -1,23 +1,79 @@
import Fastify, { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify'
import { HttpAdapter, HttpResponse } from './api'
import { HttpServer } from '../../http'
import Fastify, { type FastifyReply, type FastifyRequest, type HookHandlerDoneFunction } from 'fastify'
import { readFileSync } from 'fs'
import { constants as HttpConstants } from 'http2'
import { isAuthenticated } from '../../security'
import type { HttpAdapter, HttpResponse, HttpServer } from './api'
import fastifyAuth from '@fastify/auth'
import fastifyBasicAuth from '@fastify/basic-auth'
function adaptResponseToReply(response: HttpResponse, reply: FastifyReply): void {
if (response.statusCode) {
void reply.code(response.statusCode)
}
if (response.body) {
void reply.send(response.body)
}
if (response.headers) {
void reply.headers(response.headers)
}
if (response.body) {
void reply.send(response.body)
}
}
export const serve: HttpAdapter = async (server: HttpServer) => {
const fastify = Fastify({
logger: server.debug,
function formatCombinedLog(request: FastifyRequest, reply: FastifyReply): string {
const remoteAddress = [request.socket.remoteAddress, request.socket.remotePort].filter((v) => v != null).join(':')
const userAgent = request.headers['user-agent'] || ''
const contentType = request.headers['content-type'] || ''
return `${remoteAddress} - "${request.method} ${request.url} HTTP/${request.raw.httpVersion}" ${reply.statusCode} "${request.protocol}://${request.hostname}" "${userAgent}" "${contentType}"`
}
type FastifyServer = ReturnType<typeof Fastify>
function createFastify(server: HttpServer): FastifyServer {
const config = { logger: false }
if (server.config.tls_cert_file && server.config.tls_key_file) {
server.log?.debug('Running with TLS enabled')
return Fastify({
...config,
https: {
key: readFileSync(server.config.tls_key_file),
cert: readFileSync(server.config.tls_cert_file),
},
})
}
return Fastify({
...config,
serverFactory: server.serverFactory,
})
}
export const fastifyServe: HttpAdapter = async (server: HttpServer) => {
const fastify = createFastify(server)
if (server.config.basic_auth && Object.keys(server.config.basic_auth).length > 0) {
const users = server.config.basic_auth
const validate = async (username: string, password: string) => {
if (!(await isAuthenticated(username, password, users))) {
throw new Error('Unauthorized')
}
}
await fastify.register(fastifyAuth)
await fastify.register(fastifyBasicAuth, { validate, authenticate: true })
fastify.after(() => {
fastify.addHook('preHandler', fastify.auth([fastify.basicAuth]))
})
}
fastify.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply) => {
if (reply.statusCode >= HttpConstants.HTTP_STATUS_BAD_REQUEST) {
server.log?.error(formatCombinedLog(request, reply))
} else if (server.config.debug) {
server.log?.debug(formatCombinedLog(request, reply))
}
})
fastify.addHook('onRequest', (request: FastifyRequest, reply: FastifyReply, next: HookHandlerDoneFunction) => {
@ -42,7 +98,7 @@ export const serve: HttpAdapter = async (server: HttpServer) => {
adaptResponseToReply(server.onMetrics(), reply)
})
await fastify.listen({ port: server.port, host: '::' })
await listen(fastify, server.config.port, server.config.interface)
return {
shutdown() {
@ -50,3 +106,13 @@ export const serve: HttpAdapter = async (server: HttpServer) => {
},
}
}
async function listen(fastify: FastifyServer, port: number, host: string): Promise<void> {
try {
await fastify.listen({ port, host })
} catch (e: unknown) {
if (host === '::' && e instanceof Error && (e as Error & { code: string }).code === 'EAFNOSUPPORT') {
await listen(fastify, port, '0.0.0.0')
}
}
}

View file

@ -0,0 +1,2 @@
export * from './api'
export * from './fastify'

9
src/ambient.d.ts vendored
View file

@ -13,3 +13,12 @@ declare module 'hap-node-client' {
on(event: 'Ready', callback: (v: unknown) => void): void
}
}
declare module 'array.prototype.group' {
function shim(): void
}
interface Array<T> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
group<U>(fn: (value: T, index: number, array: T[]) => U, thisArg?: any): { U: T[] }
}

52
src/boundaries/checker.ts Normal file
View file

@ -0,0 +1,52 @@
import type z from 'zod'
type Path = (string | number)[]
function resolvePath(data: unknown, path: Path): { resolvedValue: string; resolvedPath: Path } {
const resolvedPath: Path = []
for (const element of path) {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (data[element] != null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data = data[element]
resolvedPath.push(element)
} else {
break
}
} catch (e) {
break
}
}
return { resolvedValue: JSON.stringify(data), resolvedPath }
}
function formatPath(path: Path): string {
return path.map((element) => (typeof element === 'number' ? `[${element}]` : element)).join('.')
}
export function checkBoundary<Output, T extends z.ZodType<Output>>(type: T, data: unknown): z.infer<T> {
const result = type.safeParse(data)
if (result.success) {
return result.data
}
const message =
'Error checking type. Details: ' +
result.error.issues
.map((issue) => ({ ...issue, ...resolvePath(data, issue.path) }))
.map(
(issue) =>
`[${issue.code}] ${issue.message}${
issue.path.length > 0 ? ` at path "${formatPath(issue.path)}"` : ''
} (data${
issue.resolvedPath.length > 0 ? ` at resolved path "${formatPath(issue.resolvedPath)}"` : ''
} is "${issue.resolvedValue}")`,
)
.join(' | ')
throw new Error(message)
}

View file

@ -1,19 +1,5 @@
import { z } from 'zod'
import { ConfigBoundary as ConfigBoundaryWithoutPlatform } from '../generated/config_boundary'
export const ConfigBoundary = z.object({
pin: z.string().regex(new RegExp('^\\d{3}-\\d{2}-\\d{3}$')).describe('Homebridge PIN for service authentication'),
debug: z.boolean().default(false),
prefix: z.string().default('homebridge'),
port: z.number().int().describe('TCP port for the prometheus probe server to listen to').default(36123),
refresh_interval: z.number().int().describe('Discover new services every <interval> seconds').default(60),
request_timeout: z
.number()
.int()
.describe('Request timeout when interacting with homebridge instances')
.default(10),
discovery_timeout: z
.number()
.int()
.describe('Discovery timeout after which the current discovery is considered failed')
.default(20),
})
export const ConfigBoundary = z.intersection(ConfigBoundaryWithoutPlatform, z.object({ platform: z.string() }))
export type Config = z.infer<typeof ConfigBoundary>

View file

@ -1,4 +1,4 @@
import z from 'zod'
import z, { type ZodNull, type ZodOptional, type ZodType, type ZodUnion } from 'zod'
const NumericTypesBoundary = z.union([
z.literal('bool'),
@ -11,17 +11,21 @@ const NumericTypesBoundary = z.union([
])
export type NumericTypes = z.infer<typeof NumericTypesBoundary>
function optionalNullable<T extends ZodType>(type: T): ZodOptional<ZodUnion<[ZodNull, T]>> {
return z.optional(z.union([z.null(), type]))
}
export const CharacteristicBoundary = z.intersection(
z.object({ type: z.string(), description: z.string() }),
z.union([
z.object({
format: NumericTypesBoundary,
value: z.optional(z.number()),
unit: z.optional(z.string()),
value: optionalNullable(z.number()),
}),
z.object({ format: z.literal('string'), value: z.string() }),
z.object({ format: z.literal('data'), value: z.optional(z.string()) }),
z.object({ format: z.literal('tlv8'), value: z.string(z.string()) }),
z.object({ format: z.literal('string'), value: optionalNullable(z.string()) }),
z.object({ format: z.literal('data'), value: optionalNullable(z.string()) }),
z.object({ format: z.literal('tlv8'), value: optionalNullable(z.string()) }),
]),
)
export type Characteristic = z.infer<typeof CharacteristicBoundary>

View file

@ -1,6 +1,3 @@
import z from 'zod'
import { ConfigBoundary as BaseConfigBoundary } from './config'
export * from './checker'
export * from './hap'
export const ConfigBoundary = z.intersection(BaseConfigBoundary, z.object({ platform: z.string() }))
export type Config = z.infer<typeof ConfigBoundary>
export * from './config'

View file

@ -0,0 +1,35 @@
// Auto-generated by "code-generation/config-scheme-gen.js", dont manually edit
import { z } from 'zod'
export const ConfigBoundary = z.object({
pin: z.string().regex(new RegExp('^\\d{3}-\\d{2}-\\d{3}$')).describe('Homebridge PIN for service authentication'),
debug: z.boolean().default(false),
prefix: z.string().default('homebridge'),
port: z.number().int().describe('TCP port where the Prometheus metrics server listens').default(36123),
interface: z
.string()
.describe(
'Interface where the Prometheus metrics server listens. Can be an IP, a hostname, "0.0.0.0" for all IPv4 interfaces, "::1" for all IPv6 interfaces. Default is "::" which means "any interface"',
)
.default('::'),
refresh_interval: z.number().int().describe('Discover new services every <interval> seconds').default(60),
request_timeout: z
.number()
.int()
.describe('Request timeout when interacting with homebridge instances')
.default(10),
discovery_timeout: z
.number()
.int()
.describe('Discovery timeout after which the current discovery is considered failed')
.default(20),
tls_cert_file: z.string().describe('Path to TLS certificate file (in PEM format)').optional(),
tls_key_file: z.string().describe('Path to TLS key file').optional(),
basic_auth: z
.record(z.string())
.describe(
'Usernames and passwords for basic auth. Object key is the username, object value is the password. Password must be encoded with bcrypt. Example: {"joanna": "$2a$12$5/mmmRB28wg9yzaXhee5Iupq3UrFr/qMgAe9LvAxGoY5jLcfVGTUq"}',
)
.optional(),
})

164
src/generated/services.ts Normal file
View file

@ -0,0 +1,164 @@
// Auto-generated by "code-generation/hap-gen.js", dont manually edit
export const Uuids = {
'00000260-0000-1000-8000-0026BB765291': 'AccessCode',
'000000DA-0000-1000-8000-0026BB765291': 'AccessControl',
'0000003E-0000-1000-8000-0026BB765291': 'AccessoryInformation',
'00000270-0000-1000-8000-0026BB765291': 'AccessoryMetrics',
'00000239-0000-1000-8000-0026BB765291': 'AccessoryRuntimeInformation',
'000000BB-0000-1000-8000-0026BB765291': 'AirPurifier',
'0000008D-0000-1000-8000-0026BB765291': 'AirQualitySensor',
'00000267-0000-1000-8000-0026BB765291': 'AssetUpdate',
'0000026A-0000-1000-8000-0026BB765291': 'Assistant',
'00000127-0000-1000-8000-0026BB765291': 'AudioStreamManagement',
'00000096-0000-1000-8000-0026BB765291': 'Battery',
'000000A1-0000-1000-8000-0026BB765291': 'BridgeConfiguration',
'00000062-0000-1000-8000-0026BB765291': 'BridgingState',
'00000111-0000-1000-8000-0026BB765291': 'CameraControl',
'0000021A-0000-1000-8000-0026BB765291': 'CameraOperatingMode',
'00000204-0000-1000-8000-0026BB765291': 'CameraRecordingManagement',
'00000110-0000-1000-8000-0026BB765291': 'CameraRTPStreamManagement',
'00000097-0000-1000-8000-0026BB765291': 'CarbonDioxideSensor',
'0000007F-0000-1000-8000-0026BB765291': 'CarbonMonoxideSensor',
'0000005A-0000-1000-8000-0026BB765291': 'CloudRelay',
'00000080-0000-1000-8000-0026BB765291': 'ContactSensor',
'00000129-0000-1000-8000-0026BB765291': 'DataStreamTransportManagement',
'00000237-0000-1000-8000-0026BB765291': 'Diagnostics',
'00000081-0000-1000-8000-0026BB765291': 'Door',
'00000121-0000-1000-8000-0026BB765291': 'Doorbell',
'00000040-0000-1000-8000-0026BB765291': 'Fan',
'000000B7-0000-1000-8000-0026BB765291': 'Fanv2',
'000000D7-0000-1000-8000-0026BB765291': 'Faucet',
'000000BA-0000-1000-8000-0026BB765291': 'FilterMaintenance',
'00000236-0000-1000-8000-0026BB765291': 'FirmwareUpdate',
'00000041-0000-1000-8000-0026BB765291': 'GarageDoorOpener',
'000000BC-0000-1000-8000-0026BB765291': 'HeaterCooler',
'000000BD-0000-1000-8000-0026BB765291': 'HumidifierDehumidifier',
'00000082-0000-1000-8000-0026BB765291': 'HumiditySensor',
'000000D9-0000-1000-8000-0026BB765291': 'InputSource',
'000000CF-0000-1000-8000-0026BB765291': 'IrrigationSystem',
'00000083-0000-1000-8000-0026BB765291': 'LeakSensor',
'00000043-0000-1000-8000-0026BB765291': 'Lightbulb',
'00000084-0000-1000-8000-0026BB765291': 'LightSensor',
'00000044-0000-1000-8000-0026BB765291': 'LockManagement',
'00000045-0000-1000-8000-0026BB765291': 'LockMechanism',
'00000112-0000-1000-8000-0026BB765291': 'Microphone',
'00000085-0000-1000-8000-0026BB765291': 'MotionSensor',
'00000266-0000-1000-8000-0026BB765291': 'NFCAccess',
'00000086-0000-1000-8000-0026BB765291': 'OccupancySensor',
'00000047-0000-1000-8000-0026BB765291': 'Outlet',
'00000055-0000-1000-8000-0026BB765291': 'Pairing',
'00000221-0000-1000-8000-0026BB765291': 'PowerManagement',
'000000A2-0000-1000-8000-0026BB765291': 'ProtocolInformation',
'0000007E-0000-1000-8000-0026BB765291': 'SecuritySystem',
'000000CC-0000-1000-8000-0026BB765291': 'ServiceLabel',
'00000133-0000-1000-8000-0026BB765291': 'Siri',
'00000253-0000-1000-8000-0026BB765291': 'SiriEndpoint',
'000000B9-0000-1000-8000-0026BB765291': 'Slats',
'00000228-0000-1000-8000-0026BB765291': 'SmartSpeaker',
'00000087-0000-1000-8000-0026BB765291': 'SmokeSensor',
'00000113-0000-1000-8000-0026BB765291': 'TelevisionSpeaker',
'00000088-0000-1000-8000-0026BB765291': 'StatefulProgrammableSwitch',
'00000089-0000-1000-8000-0026BB765291': 'StatelessProgrammableSwitch',
'00000049-0000-1000-8000-0026BB765291': 'Switch',
'0000022E-0000-1000-8000-0026BB765291': 'TapManagement',
'00000125-0000-1000-8000-0026BB765291': 'TargetControl',
'00000122-0000-1000-8000-0026BB765291': 'TargetControlManagement',
'000000D8-0000-1000-8000-0026BB765291': 'Television',
'0000008A-0000-1000-8000-0026BB765291': 'TemperatureSensor',
'0000004A-0000-1000-8000-0026BB765291': 'Thermostat',
'00000701-0000-1000-8000-0026BB765291': 'ThreadTransport',
'00000099-0000-1000-8000-0026BB765291': 'TimeInformation',
'00000203-0000-1000-8000-0026BB765291': 'TransferTransportManagement',
'00000056-0000-1000-8000-0026BB765291': 'Tunnel',
'000000D0-0000-1000-8000-0026BB765291': 'Valve',
'0000020A-0000-1000-8000-0026BB765291': 'WiFiRouter',
'0000020F-0000-1000-8000-0026BB765291': 'WiFiSatellite',
'0000022A-0000-1000-8000-0026BB765291': 'WiFiTransport',
'0000008B-0000-1000-8000-0026BB765291': 'Window',
'0000008C-0000-1000-8000-0026BB765291': 'WindowCovering',
} as const
export const Services = {
AccessCode: '00000260-0000-1000-8000-0026BB765291',
AccessControl: '000000DA-0000-1000-8000-0026BB765291',
AccessoryInformation: '0000003E-0000-1000-8000-0026BB765291',
AccessoryMetrics: '00000270-0000-1000-8000-0026BB765291',
AccessoryRuntimeInformation: '00000239-0000-1000-8000-0026BB765291',
AirPurifier: '000000BB-0000-1000-8000-0026BB765291',
AirQualitySensor: '0000008D-0000-1000-8000-0026BB765291',
AssetUpdate: '00000267-0000-1000-8000-0026BB765291',
Assistant: '0000026A-0000-1000-8000-0026BB765291',
AudioStreamManagement: '00000127-0000-1000-8000-0026BB765291',
BatteryService: '00000096-0000-1000-8000-0026BB765291',
Battery: '00000096-0000-1000-8000-0026BB765291',
BridgeConfiguration: '000000A1-0000-1000-8000-0026BB765291',
BridgingState: '00000062-0000-1000-8000-0026BB765291',
CameraControl: '00000111-0000-1000-8000-0026BB765291',
CameraOperatingMode: '0000021A-0000-1000-8000-0026BB765291',
CameraEventRecordingManagement: '00000204-0000-1000-8000-0026BB765291',
CameraRecordingManagement: '00000204-0000-1000-8000-0026BB765291',
CameraRTPStreamManagement: '00000110-0000-1000-8000-0026BB765291',
CarbonDioxideSensor: '00000097-0000-1000-8000-0026BB765291',
CarbonMonoxideSensor: '0000007F-0000-1000-8000-0026BB765291',
Relay: '0000005A-0000-1000-8000-0026BB765291',
CloudRelay: '0000005A-0000-1000-8000-0026BB765291',
ContactSensor: '00000080-0000-1000-8000-0026BB765291',
DataStreamTransportManagement: '00000129-0000-1000-8000-0026BB765291',
Diagnostics: '00000237-0000-1000-8000-0026BB765291',
Door: '00000081-0000-1000-8000-0026BB765291',
Doorbell: '00000121-0000-1000-8000-0026BB765291',
Fan: '00000040-0000-1000-8000-0026BB765291',
Fanv2: '000000B7-0000-1000-8000-0026BB765291',
Faucet: '000000D7-0000-1000-8000-0026BB765291',
FilterMaintenance: '000000BA-0000-1000-8000-0026BB765291',
FirmwareUpdate: '00000236-0000-1000-8000-0026BB765291',
GarageDoorOpener: '00000041-0000-1000-8000-0026BB765291',
HeaterCooler: '000000BC-0000-1000-8000-0026BB765291',
HumidifierDehumidifier: '000000BD-0000-1000-8000-0026BB765291',
HumiditySensor: '00000082-0000-1000-8000-0026BB765291',
InputSource: '000000D9-0000-1000-8000-0026BB765291',
IrrigationSystem: '000000CF-0000-1000-8000-0026BB765291',
LeakSensor: '00000083-0000-1000-8000-0026BB765291',
Lightbulb: '00000043-0000-1000-8000-0026BB765291',
LightSensor: '00000084-0000-1000-8000-0026BB765291',
LockManagement: '00000044-0000-1000-8000-0026BB765291',
LockMechanism: '00000045-0000-1000-8000-0026BB765291',
Microphone: '00000112-0000-1000-8000-0026BB765291',
MotionSensor: '00000085-0000-1000-8000-0026BB765291',
NFCAccess: '00000266-0000-1000-8000-0026BB765291',
OccupancySensor: '00000086-0000-1000-8000-0026BB765291',
Outlet: '00000047-0000-1000-8000-0026BB765291',
Pairing: '00000055-0000-1000-8000-0026BB765291',
PowerManagement: '00000221-0000-1000-8000-0026BB765291',
ProtocolInformation: '000000A2-0000-1000-8000-0026BB765291',
SecuritySystem: '0000007E-0000-1000-8000-0026BB765291',
ServiceLabel: '000000CC-0000-1000-8000-0026BB765291',
Siri: '00000133-0000-1000-8000-0026BB765291',
SiriEndpoint: '00000253-0000-1000-8000-0026BB765291',
Slat: '000000B9-0000-1000-8000-0026BB765291',
Slats: '000000B9-0000-1000-8000-0026BB765291',
SmartSpeaker: '00000228-0000-1000-8000-0026BB765291',
SmokeSensor: '00000087-0000-1000-8000-0026BB765291',
Speaker: '00000113-0000-1000-8000-0026BB765291',
StatefulProgrammableSwitch: '00000088-0000-1000-8000-0026BB765291',
StatelessProgrammableSwitch: '00000089-0000-1000-8000-0026BB765291',
Switch: '00000049-0000-1000-8000-0026BB765291',
TapManagement: '0000022E-0000-1000-8000-0026BB765291',
TargetControl: '00000125-0000-1000-8000-0026BB765291',
TargetControlManagement: '00000122-0000-1000-8000-0026BB765291',
Television: '000000D8-0000-1000-8000-0026BB765291',
TelevisionSpeaker: '00000113-0000-1000-8000-0026BB765291',
TemperatureSensor: '0000008A-0000-1000-8000-0026BB765291',
Thermostat: '0000004A-0000-1000-8000-0026BB765291',
ThreadTransport: '00000701-0000-1000-8000-0026BB765291',
TimeInformation: '00000099-0000-1000-8000-0026BB765291',
TransferTransportManagement: '00000203-0000-1000-8000-0026BB765291',
TunneledBTLEAccessoryService: '00000056-0000-1000-8000-0026BB765291',
Tunnel: '00000056-0000-1000-8000-0026BB765291',
Valve: '000000D0-0000-1000-8000-0026BB765291',
WiFiRouter: '0000020A-0000-1000-8000-0026BB765291',
WiFiSatellite: '0000020F-0000-1000-8000-0026BB765291',
WiFiTransport: '0000022A-0000-1000-8000-0026BB765291',
Window: '0000008B-0000-1000-8000-0026BB765291',
WindowCovering: '0000008C-0000-1000-8000-0026BB765291',
} as const

View file

@ -1,14 +0,0 @@
import { HttpResponse } from './adapters/http/api'
import { Metric } from './metrics'
import { Logger } from 'homebridge'
export interface HttpServer {
port: number
debug: boolean
log: Logger
onRequest(): HttpResponse | undefined
onMetrics(): HttpResponse
onNotFound(): HttpResponse
onError(error: unknown): HttpResponse
updateMetrics(metrics: Metric[]): void
}

View file

@ -1,4 +1,4 @@
import { API } from 'homebridge'
import type { API } from 'homebridge'
import { PLATFORM_NAME } from './settings'
import { PrometheusExporterPlatform } from './platform'

View file

@ -1,14 +1,15 @@
import type { Accessory, Device, Service } from './boundaries/hap'
import { assertTypeExhausted, isType } from './std'
// eslint-disable-next-line import/no-extraneous-dependencies
import { Service as HapService } from 'hap-nodejs'
import type { Accessory, Device, Service } from './boundaries'
import { Services, Uuids } from './generated/services'
import { assertTypeExhausted, isKeyOfConstObject, isType, strCamelCaseToSnakeCase } from './std'
type Labels = Record<string, string>
export class Metric {
constructor(
public readonly name: string,
public readonly value: number,
public readonly timestamp: Date | null = null,
public readonly labels: Record<string, string> = {},
public readonly labels: Labels = {},
) {}
}
@ -18,7 +19,7 @@ export class Metric {
const METRICS_FILTER = ['Identifier']
export function aggregate(devices: Device[], timestamp: Date): Metric[] {
const metrics: Metric[] = []
const metrics: Metric[][] = []
for (const device of devices) {
for (const accessory of device.accessories.accessories) {
@ -28,87 +29,91 @@ export function aggregate(devices: Device[], timestamp: Date): Metric[] {
...getAccessoryLabels(accessory),
...getServiceLabels(service),
}
for (const characteristic of service.characteristics) {
const format = characteristic.format
switch (format) {
case 'string':
case 'tlv8':
case 'data':
break
case 'bool':
case 'float':
case 'int':
case 'uint8':
case 'uint16':
case 'uint32':
case 'uint64':
if (typeof characteristic.value !== 'undefined') {
if (METRICS_FILTER.includes(characteristic.description)) {
break
}
const name = formatName(
uuidToServerName(service.type),
characteristic.description,
characteristic.unit,
)
metrics.push(new Metric(name, characteristic.value, timestamp, labels))
}
break
default:
assertTypeExhausted(format)
}
}
metrics.push(extractMetrics(service, timestamp, labels))
}
}
}
return metrics.flat()
}
function extractMetrics(service: Service, timestamp: Date, labels: Labels): Metric[] {
const metrics: Metric[] = []
for (const characteristic of service.characteristics) {
if (METRICS_FILTER.includes(characteristic.description)) {
continue
}
if (characteristic.value == null) {
continue
}
const format = characteristic.format
switch (format) {
case 'string':
case 'tlv8':
case 'data':
break
case 'bool':
case 'float':
case 'int':
case 'uint8':
case 'uint16':
case 'uint32':
case 'uint64':
{
const name = formatName(
isKeyOfConstObject(service.type, Uuids) ? Uuids[service.type] : 'custom',
characteristic.description,
characteristic.unit,
)
metrics.push(new Metric(name, characteristic.value, timestamp, labels))
}
break
default:
assertTypeExhausted(format)
}
}
return metrics
}
export function formatName(serviceName: string, description: string, unit: string | undefined = undefined): string {
export function formatName(serviceName: string, description: string, unit: string | null = null): string {
return (
[serviceName, description, typeof unit === 'string' ? unit.toLowerCase() : undefined]
.filter(isType('string'))
.map((v) => camelCaseToSnakeCase(v))
.map((val) => strCamelCaseToSnakeCase(val))
// Remove duplicate prefix
.reduce((carry, value) => (value.startsWith(carry) ? value : carry + '_' + value))
.reduce((carry, val) => (val.startsWith(carry) ? val : `${carry}_${val}`))
)
}
function camelCaseToSnakeCase(str: string): string {
return str
.replace(/\B([A-Z][a-z])/g, ' $1')
.toLowerCase()
.trim()
.replace(/\s+/g, '_')
}
function getDeviceLabels(device: Device): Record<string, string> {
function getDeviceLabels(device: Device): Labels {
return {
bridge: device.instance.name,
device_id: device.instance.deviceID,
}
}
function getAccessoryLabels(accessory: Accessory): Record<string, string> {
const labels: Record<string, string> = {}
function getAccessoryLabels(accessory: Accessory): Labels {
for (const service of accessory.services) {
if (service.type === '0000003E-0000-1000-8000-0026BB765291') {
if (service.type === Services.AccessoryInformation) {
return getServiceLabels(service)
}
}
return labels
return {}
}
function getServiceLabels(service: Service): Record<string, string> {
const labels: Record<string, string> = {}
function getServiceLabels(service: Service): Labels {
const labels: Labels = {}
for (const characteristic of service.characteristics) {
if (
characteristic.value != null &&
characteristic.format === 'string' &&
[
'Name',
@ -121,21 +126,9 @@ function getServiceLabels(service: Service): Record<string, string> {
'Hardware Revision',
].includes(characteristic.description)
) {
labels[camelCaseToSnakeCase(characteristic.description)] = characteristic.value
labels[strCamelCaseToSnakeCase(characteristic.description)] = characteristic.value
}
}
return labels
}
function uuidToServerName(uuid: string): string {
for (const name of Object.getOwnPropertyNames(HapService)) {
const maybeService = (HapService as unknown as Record<string, unknown>)[name]
if (typeof maybeService === 'function' && 'UUID' in maybeService) {
if ((maybeService as Record<string, string>)['UUID'] === uuid) {
return name
}
}
}
return 'custom'
}

View file

@ -1,21 +1,20 @@
import { API, IndependentPlatformPlugin, Logger, PlatformConfig } from 'homebridge'
import type { API, IndependentPlatformPlugin, Logger, PlatformConfig } from 'homebridge'
import { aggregate } from './metrics'
import { discover } from './adapters/discovery/hap_node_js_client'
import { serve } from './adapters/http/fastify'
import { HttpServerController } from './adapters/http/api'
import { hapNodeJsClientDiscover as discover } from './adapters/discovery'
import { type HttpServerController, fastifyServe as serve } from './adapters/http'
import { PrometheusServer } from './prometheus'
import { Config, ConfigBoundary } from './boundaries'
import { type Config, ConfigBoundary, checkBoundary } from './boundaries'
export class PrometheusExporterPlatform implements IndependentPlatformPlugin {
private readonly httpServer: PrometheusServer
private httpServerController: HttpServerController | undefined = undefined
private httpServerController: HttpServerController | null = null
private readonly config: Config
constructor(public readonly log: Logger, config: PlatformConfig, public readonly api: API) {
this.log.debug('Initializing platform %s', config.platform)
this.config = ConfigBoundary.parse(config)
this.config = checkBoundary(ConfigBoundary, config)
this.log.debug('Configuration parsed', this.config)
@ -28,7 +27,7 @@ export class PrometheusExporterPlatform implements IndependentPlatformPlugin {
this.log.debug('Starting Prometheus HTTP server on port %d', this.config.port)
this.httpServer = new PrometheusServer(this.config.port, this.log, this.config.debug, this.config.prefix)
this.httpServer = new PrometheusServer(this.config, this.log)
serve(this.httpServer)
.then((httpServerController) => {
this.log.debug('HTTP server started on port %d', this.config.port)
@ -46,17 +45,10 @@ export class PrometheusExporterPlatform implements IndependentPlatformPlugin {
private startHapDiscovery(): void {
this.log.debug('Starting HAP discovery')
discover({
logger: this.log,
refreshInterval: this.config.refresh_interval,
discoveryTimeout: this.config.discovery_timeout,
requestTimeout: this.config.request_timeout,
pin: this.config.pin,
debug: this.config.debug,
})
discover({ log: this.log, config: this.config })
.then((devices) => {
const metrics = aggregate(devices, new Date())
this.httpServer.updateMetrics(metrics)
this.httpServer.onMetricsDiscovery(metrics)
this.log.debug('HAP discovery completed, %d metrics discovered', metrics.length)
this.startHapDiscovery()
})

View file

@ -1,24 +1,39 @@
import { Metric } from './metrics'
import { Logger } from 'homebridge'
import { HttpResponse } from './adapters/http/api'
import { HttpServer } from './http'
import type { Logger } from 'homebridge'
import type { HttpConfig, HttpResponse, HttpServer } from './adapters/http'
import type { Metric } from './metrics'
import { strTrimRight } from './std'
import { shim } from 'array.prototype.group'
shim()
export class MetricsRenderer {
constructor(private readonly prefix: string) {}
private readonly prefix: string
render(metric: Metric): string {
const name = this.metricName(metric.name)
return [
`# TYPE ${name} ${name.endsWith('_total') ? 'counter' : 'gauge'}`,
`${name}${this.renderLabels(metric.labels)} ${metric.value}${
metric.timestamp !== null ? ' ' + String(metric.timestamp.getTime()) : ''
}`,
].join('\n')
constructor(prefix: string) {
this.prefix = strTrimRight(prefix, '_')
}
private renderLabels(labels: Metric['labels']): string {
render(metrics: Metric[]): string {
return (
Object.entries(metrics.sort().group((metric) => this.metricName(metric.name)))
.map(([name, metrics]) => {
return [
`# TYPE ${name} ${name.endsWith('_total') ? 'counter' : 'gauge'}`,
metrics.map((metric) => this.formatMetric(metric)).join('\n'),
].join('\n')
})
.join('\n\n') + '\n'
)
}
private formatMetric(metric: Metric): string {
return `${this.metricName(metric.name)}${MetricsRenderer.renderLabels(metric.labels)} ${metric.value}${
metric.timestamp !== null ? ' ' + String(metric.timestamp.getTime()) : ''
}`
}
private static renderLabels(labels: Metric['labels']): string {
const rendered = Object.entries(labels)
.map(([key, value]) => `${sanitizePrometheusMetricName(key)}="${escapeAttributeValue(value)}"`)
.map(([label, val]) => `${sanitizePrometheusMetricName(label)}="${escapeAttributeValue(val)}"`)
.join(',')
return rendered !== '' ? '{' + rendered + '}' : ''
@ -27,64 +42,68 @@ export class MetricsRenderer {
private metricName(name: string): string {
name = name.replace(/^(.*_)?(total)_(.*)$/, '$1$3_$2')
return sanitizePrometheusMetricName(this.prefix.replace(/_+$/, '') + '_' + name)
return sanitizePrometheusMetricName(`${this.prefix}_${name}`)
}
}
const contentTypeHeader = { 'Content-Type': 'text/plain; charset=UTF-8' }
const retryAfterWhileDiscovery = 15
const textContentType = 'text/plain; charset=utf-8'
const prometheusSpecVersion = '0.0.4'
const metricsContentType = `${textContentType}; version=${prometheusSpecVersion}`
function withHeaders(contentType: string, headers: Record<string, string> = {}): Record<string, string> {
return { ...headers, 'Content-Type': contentType }
}
export class PrometheusServer implements HttpServer {
private metricsInitialized = false
private metrics: Metric[] = []
private metricsDiscovered = false
private metricsResponse = ''
constructor(
public readonly port: number,
public readonly log: Logger,
public readonly debug: boolean,
private readonly prefix: string,
public readonly config: HttpConfig,
public readonly log: Logger | null = null,
private readonly renderer: MetricsRenderer = new MetricsRenderer(config.prefix),
) {}
onRequest(): HttpResponse | undefined {
if (!this.metricsInitialized) {
return {
statusCode: 503,
headers: { ...contentTypeHeader, 'Retry-After': '10' },
body: 'Metrics discovery pending',
}
onRequest(): HttpResponse | null {
if (this.metricsDiscovered) {
return null
}
return {
statusCode: 503,
headers: withHeaders(textContentType, { 'Retry-After': String(retryAfterWhileDiscovery) }),
body: 'Metrics discovery pending',
}
}
onMetrics(): HttpResponse {
const renderer = new MetricsRenderer(this.prefix)
const metrics = this.metrics.map((metric) => renderer.render(metric)).join('\n')
return {
statusCode: 200,
headers: contentTypeHeader,
body: metrics,
headers: withHeaders(metricsContentType),
body: this.metricsResponse,
}
}
onNotFound(): HttpResponse {
return {
statusCode: 404,
headers: contentTypeHeader,
headers: withHeaders(textContentType),
body: 'Not found. Try /metrics',
}
}
onError(error: unknown): HttpResponse {
this.log.error('HTTP request error: %o', error)
onError(error: Error): HttpResponse {
this.log?.error('HTTP request error: %o', error)
return {
statusCode: 500,
headers: contentTypeHeader,
body: 'Server error',
headers: withHeaders(textContentType),
body: error.message,
}
}
updateMetrics(metrics: Metric[]): void {
this.metrics = metrics
this.metricsInitialized = true
onMetricsDiscovery(metrics: Metric[]): void {
this.metricsResponse = this.renderer.render(metrics)
this.metricsDiscovered = true
}
}
@ -100,7 +119,7 @@ function escapeString(str: string) {
*
* `undefined` is converted to an empty string.
*/
function escapeAttributeValue(str: string) {
function escapeAttributeValue(str: Metric['labels'][keyof Metric['labels']]) {
if (typeof str !== 'string') {
str = JSON.stringify(str)
}

9
src/security.ts Normal file
View file

@ -0,0 +1,9 @@
import { compare } from 'bcrypt'
export function isAuthenticated(
username: string,
plainPassword: string,
map: Record<string, string>,
): Promise<boolean> {
return compare(plainPassword, map[username] || '')
}

View file

@ -1,15 +1,41 @@
type Types = 'string' | 'number' | 'boolean' | 'object'
interface TypeMap {
string: string
number: number
bigint: bigint
boolean: boolean
object: object
symbol: symbol
undefined: undefined
}
export function isType<T extends Types>(type: T): (v: unknown) => v is TypeMap[T] {
// Type predicate higher order function for use with e.g. filter or map
export function isType<T extends keyof TypeMap>(type: T): (v: unknown) => v is TypeMap[T] {
return (v: unknown): v is TypeMap[T] => typeof v === type
}
// Type predicate for object keys
// Only safe for const objects, as other objects might carry additional, undeclared properties
export function isKeyOfConstObject<T extends object>(key: string | number | symbol, obj: T): key is keyof T {
return key in obj
}
// Use for exhaustiveness checks in switch/case
export function assertTypeExhausted(v: never): never {
throw new Error(`Type should be exhausted but is not. Value "${JSON.stringify(v)}`)
}
export function strCamelCaseToSnakeCase(str: string): string {
return str
.replace(/\B([A-Z][a-z])/g, ' $1')
.toLowerCase()
.trim()
.replace(/\s+/g, '_')
}
export function strReverse(str: string): string {
return str.split('').reverse().join('')
}
export function strTrimRight(str: string, char: string): string {
return strReverse(strReverse(str).replace(new RegExp(`^[${char}]+`), ''))
}

View file

@ -0,0 +1,74 @@
import { afterAll, describe, expect, jest, test } from '@jest/globals'
import { hapNodeJsClientDiscover as discover } from '../../../src/adapters/discovery/hap_node_js_client'
const intervals: NodeJS.Timer[] = []
let deviceData: unknown = null
jest.mock('hap-node-client', () => ({
HAPNodeJSClient: class {
on(event: string, fn: (data: unknown) => void) {
intervals.push(setInterval(() => fn(deviceData), 100))
}
},
}))
const properDeviceData = {
instance: {
deviceID: 'bff926c2-ddbe-4141-b17f-f011e03e669c',
name: 'name',
url: 'http://bridge.local',
},
accessories: {
accessories: [
{
services: [
{
type: 'SERVICE TYPE',
characteristics: [
{
format: 'bool',
value: 1,
description: 'description',
type: 'CHARACTERISTIC TYPE',
},
],
},
],
},
],
},
}
const invalidDeviceData = {}
const config = {
debug: false,
pin: '123-12-123',
refresh_interval: 10,
discovery_timeout: 10,
request_timeout: 10,
}
describe('HAP NodeJS Client', () => {
afterAll(() => {
intervals.map((timer) => clearInterval(timer))
})
test('Simple discovery', async () => {
deviceData = [properDeviceData]
expect(await discover({ config })).toHaveLength(1)
})
test('Connection pooling works', async () => {
deviceData = [properDeviceData]
expect(await discover({ config })).toHaveLength(1)
expect(await discover({ config })).toHaveLength(1)
})
test('Invalid device data is ignored', async () => {
deviceData = [invalidDeviceData, properDeviceData]
expect(await discover({ config })).toHaveLength(1)
})
})

View file

@ -0,0 +1,129 @@
import { describe, test } from '@jest/globals'
import request from 'supertest'
import { PrometheusServer } from '../../../src/prometheus'
import { type HttpServer, fastifyServe } from '../../../src/adapters/http'
import { type Server, createServer } from 'http'
import { Metric } from '../../../src/metrics'
class TestablePrometheusServer extends PrometheusServer {
public serverFactory: HttpServer['serverFactory']
}
function createTestServer(): { http: Server; prometheus: HttpServer } {
return createTestServerWithBasicAuth({})
}
function createTestServerWithBasicAuth(basicAuth: Record<string, string>): { http: Server; prometheus: HttpServer } {
const http = createServer()
const prometheus = new TestablePrometheusServer({
port: 0,
interface: 'localhost',
debug: false,
prefix: 'homebridge',
basic_auth: basicAuth,
})
prometheus.serverFactory = (handler) => http.on('request', handler)
fastifyServe(prometheus).catch((err: Error) => {
if (!('code' in err) || (err as unknown as { code: unknown }).code !== 'ERR_SERVER_ALREADY_LISTEN') {
console.debug(err)
}
})
return { http, prometheus }
}
const secretAsBcrypt = '$2b$12$B8C9hsi2idheYOdSM9au0.6DbD6z44iI5dZo.72AYLsAEiNdnqNPG'
describe('Fastify HTTP adapter', () => {
test('Serves 503 everywhere while metrics are not available', () => {
return request(createTestServer().http)
.get('/any-url')
.expect(503)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Retry-After', '15')
.expect('Metrics discovery pending')
})
test('Serves 404 on / when metrics are available', () => {
const testServer = createTestServer()
testServer.prometheus.onMetricsDiscovery([])
return request(testServer.http)
.get('/')
.expect(404)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Not found. Try /metrics')
})
test('Serves metrics', () => {
const testServer = createTestServer()
const timestamp = new Date('2020-01-01 00:00:00 UTC')
testServer.prometheus.onMetricsDiscovery([
new Metric('metric', 0.1, timestamp, { name: 'metric' }),
new Metric('total_something', 100, timestamp, { name: 'counter' }),
])
return request(testServer.http)
.get('/metrics')
.expect(200)
.expect('Content-Type', 'text/plain; charset=utf-8; version=0.0.4')
.expect(
[
'# TYPE homebridge_metric gauge',
'homebridge_metric{name="metric"} 0.1 1577836800000',
'',
'# TYPE homebridge_something_total counter',
'homebridge_something_total{name="counter"} 100 1577836800000',
'',
].join('\n'),
)
})
test('Basic auth denied without user', () => {
const testServer = createTestServerWithBasicAuth({ joanna: secretAsBcrypt })
testServer.prometheus.onMetricsDiscovery([])
return request(testServer.http)
.get('/metrics')
.expect(401)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Missing or bad formatted authorization header')
})
test('Basic auth denied with incorrect user', () => {
const testServer = createTestServerWithBasicAuth({ joanna: secretAsBcrypt })
testServer.prometheus.onMetricsDiscovery([])
return request(testServer.http)
.get('/metrics')
.auth('john', 'secret')
.expect(401)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Unauthorized')
})
test('Basic auth grants access', () => {
const testServer = createTestServerWithBasicAuth({ joanna: secretAsBcrypt })
const timestamp = new Date('2020-01-01 00:00:00 UTC')
testServer.prometheus.onMetricsDiscovery([
new Metric('metric', 0.1, timestamp, { name: 'metric' }),
new Metric('total_something', 100, timestamp, { name: 'counter' }),
])
return request(testServer.http)
.get('/metrics')
.auth('joanna', 'secret')
.expect(200)
.expect('Content-Type', 'text/plain; charset=utf-8; version=0.0.4')
.expect(
[
'# TYPE homebridge_metric gauge',
'homebridge_metric{name="metric"} 0.1 1577836800000',
'',
'# TYPE homebridge_something_total counter',
'homebridge_something_total{name="counter"} 100 1577836800000',
'',
].join('\n'),
)
})
})

View file

@ -6,6 +6,7 @@ import emptyData from './fixtures/empty.json'
import tpLinkData from './fixtures/tp-link.json'
import harmonyData from './fixtures/harmony.json'
import unknownUuidData from './fixtures/issues/gh-9-unknown-uuid.json'
import nullableValueData from './fixtures/issues/gh-19-nullable-value.json'
describe('Metrics aggregator', () => {
const timestamp = new Date('2000-01-01 00:00:00 UTC')
@ -149,10 +150,10 @@ describe('Metrics aggregator', () => {
new Metric('input_source_current_visibility_state', 0, timestamp, expectedLabels3),
new Metric('input_source_target_visibility_state', 0, timestamp, expectedLabels3),
new Metric('speaker_active', 1, timestamp, expectedLabels4),
new Metric('speaker_volume_control_type', 3, timestamp, expectedLabels4),
new Metric('speaker_mute', 0, timestamp, expectedLabels4),
new Metric('speaker_volume_percentage', 50, timestamp, expectedLabels4),
new Metric('television_speaker_active', 1, timestamp, expectedLabels4),
new Metric('television_speaker_volume_control_type', 3, timestamp, expectedLabels4),
new Metric('television_speaker_mute', 0, timestamp, expectedLabels4),
new Metric('television_speaker_volume_percentage', 50, timestamp, expectedLabels4),
])
})
@ -171,4 +172,10 @@ describe('Metrics aggregator', () => {
new Metric('custom_restart', 0, timestamp, expectedLabels),
])
})
test('Aggregates metrics with nullable values', () => {
const unknowmnValue = DeviceBoundary.parse(nullableValueData)
expect(aggregate([unknowmnValue], timestamp)).toEqual([])
})
})

6
tests/ambient.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
import type { SuperTest, Test } from 'supertest'
import type { Server } from 'http'
declare module 'supertest' {
function supertest(app: Server): SuperTest<Test>
}

View file

@ -0,0 +1,45 @@
import { describe, expect, test } from '@jest/globals'
import z from 'zod'
import { checkBoundary } from '../../src/boundaries'
const TestBoundary = z.object({
member: z.literal('something'),
anotherMember: z.optional(z.literal('something else')),
yetAnotherMember: z.optional(
z.array(
z.object({
member: z.literal('member'),
}),
),
),
})
describe('Test boundary checker', () => {
test('Returns checked data after successful check', () => {
const result = checkBoundary(TestBoundary, { member: 'something' })
expect(result).toEqual({ member: 'something' })
})
test('Returns error and insightful error message on failing check for simple string', () => {
expect(() => checkBoundary(z.string(), 123)).toThrow(
'[invalid_type] Expected string, received number (data is "123")',
)
})
test('Returns error and insightful error message on failing check for nested object', () => {
expect(() =>
checkBoundary(TestBoundary, {
member: 'something else',
anotherMember: 'unexpected',
yetAnotherMember: [{ foo: 123 }],
}),
).toThrow(
[
'[invalid_literal] Invalid literal value, expected "something" at path "member" (data at resolved path "member" is ""something else"") | ',
'[invalid_literal] Invalid literal value, expected "something else" at path "anotherMember" (data at resolved path "anotherMember" is ""unexpected"") | ',
'[invalid_literal] Invalid literal value, expected "member" at path "yetAnotherMember.[0].member" (data at resolved path "yetAnotherMember.[0]" is "{"foo":123}")',
].join(''),
)
})
})

View file

@ -0,0 +1,48 @@
{
"ipAddress": "192.168.0.1",
"instance": {
"host": "192.168.0.1",
"port": 51826,
"url": "http://192.168.0.1:51826",
"deviceID": "AA:AA:AA:AA:AA:AA",
"txt": {
"c#": "5",
"ff": "0",
"id": "AA:AA:AA:AA:AA:AA",
"md": "homebridge",
"pv": "1.1",
"s#": "1",
"sf": "0",
"ci": "2",
"sh": "Hv0v9A=="
},
"name": "Test bridge"
},
"accessories": {
"accessories": [
{
"aid": 3,
"services": [
{
"type": "00000012-0000-1000-8000-656261617577",
"iid": 8,
"characteristics": [
{
"type": "00000073-0000-1000-8000-0026BB765291",
"iid": 11,
"value": null,
"perms": ["ev", "pr"],
"description": "Programmable Switch Event",
"format": "uint8",
"minValue": 0,
"maxValue": 2,
"minStep": 1,
"valid-values": [0]
}
]
}
]
}
]
}
}

View file

@ -6,68 +6,94 @@ describe('Render prometheus metrics', () => {
const renderer = new MetricsRenderer('prefix')
test('Renders simple metric', () => {
expect(renderer.render(new Metric('metric', 0.000001))).toEqual(
expect(renderer.render([new Metric('metric', 0.000001)])).toEqual(
`# TYPE prefix_metric gauge
prefix_metric 0.000001`,
prefix_metric 0.000001
`,
)
})
test('Renders simple metric with timestamp', () => {
expect(renderer.render(new Metric('metric', 0.000001, new Date('2000-01-01 00:00:00 UTC')))).toEqual(
expect(renderer.render([new Metric('metric', 0.000001, new Date('2000-01-01 00:00:00 UTC'))])).toEqual(
`# TYPE prefix_metric gauge
prefix_metric 0.000001 946684800000`,
prefix_metric 0.000001 946684800000
`,
)
})
test('Renders simple metric with labels', () => {
expect(
renderer.render(
renderer.render([
new Metric('metric', 0.000001, new Date('2000-01-01 00:00:00 UTC'), { label: 'Some Label' }),
),
]),
).toEqual(
`# TYPE prefix_metric gauge
prefix_metric{label="Some Label"} 0.000001 946684800000`,
prefix_metric{label="Some Label"} 0.000001 946684800000
`,
)
})
test('Renders total as counter', () => {
for (const metricName of ['some_total_metric', 'some_metric_total', 'total_some_metric']) {
expect(
renderer.render(
renderer.render([
new Metric(metricName, 42, new Date('2000-01-01 00:00:00 UTC'), { label: 'Some Label' }),
),
]),
).toEqual(
`# TYPE prefix_some_metric_total counter
prefix_some_metric_total{label="Some Label"} 42 946684800000`,
prefix_some_metric_total{label="Some Label"} 42 946684800000
`,
)
}
})
test('Renders multiple metrics correctly', () => {
expect(
renderer.render([
new Metric('some_gauge', 10, new Date('2000-01-01 00:00:00 UTC')),
new Metric('another_gauge', 30, new Date('2000-01-01 00:00:00 UTC')),
new Metric('some_gauge', 20, new Date('2000-01-01 00:00:00 UTC')),
]),
).toEqual(
`# TYPE prefix_some_gauge gauge
prefix_some_gauge 10 946684800000
prefix_some_gauge 20 946684800000
# TYPE prefix_another_gauge gauge
prefix_another_gauge 30 946684800000
`,
)
})
test('Sanitizes metric names', () => {
expect(renderer.render(new Metric('mätric name', 0))).toEqual(
expect(renderer.render([new Metric('mätric name', 0)])).toEqual(
`# TYPE prefix_m_tric_name gauge
prefix_m_tric_name 0`,
prefix_m_tric_name 0
`,
)
})
test('Sanitizes label names', () => {
expect(renderer.render(new Metric('metric', 0, null, { 'yet another label': 'foo' }))).toEqual(
expect(renderer.render([new Metric('metric', 0, null, { 'yet another label': 'foo' })])).toEqual(
`# TYPE prefix_metric gauge
prefix_metric{yet_another_label="foo"} 0`,
prefix_metric{yet_another_label="foo"} 0
`,
)
})
test('Escapes newlines in attribute value', () => {
expect(renderer.render(new Metric('metric', 0, null, { label: 'foo\nbar' }))).toEqual(
expect(renderer.render([new Metric('metric', 0, null, { label: 'foo\nbar' })])).toEqual(
`# TYPE prefix_metric gauge
prefix_metric{label="foo\\nbar"} 0`,
prefix_metric{label="foo\\nbar"} 0
`,
)
})
test('Escapes quotes in attribute value', () => {
expect(renderer.render(new Metric('metric', 0, null, { label: 'foo"bar' }))).toEqual(
expect(renderer.render([new Metric('metric', 0, null, { label: 'foo"bar' })])).toEqual(
`# TYPE prefix_metric gauge
prefix_metric{label="foo\\"bar"} 0`,
prefix_metric{label="foo\\"bar"} 0
`,
)
})
})

View file

@ -10,8 +10,11 @@
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"verbatimModuleSyntax": false,
"noImplicitAny": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"tsBuildInfoFile": ".tsbuildinfo",
"incremental": true
},
"include": ["src/", "tests/"],
"exclude": []