@homebridge-plugins/homebridge-eufy-security 4.7.1 → 5.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AUTHORS +4 -0
  2. package/CODE_OF_CONDUCT.md +30 -0
  3. package/CONTRIBUTING.md +77 -0
  4. package/README.md +152 -49
  5. package/SECURITY.md +30 -0
  6. package/config.schema.json +92 -3
  7. package/dist/account/ownership.js +290 -0
  8. package/dist/account/ownership.js.map +1 -0
  9. package/dist/account/persistence.js +274 -0
  10. package/dist/account/persistence.js.map +1 -0
  11. package/dist/account/temporary-authentication.js +348 -0
  12. package/dist/account/temporary-authentication.js.map +1 -0
  13. package/dist/configuration.js +167 -0
  14. package/dist/configuration.js.map +1 -0
  15. package/dist/device/member-evidence.js +66 -0
  16. package/dist/device/member-evidence.js.map +1 -0
  17. package/dist/device/member-trust.js +114 -0
  18. package/dist/device/member-trust.js.map +1 -0
  19. package/dist/device/snapshot.js +190 -0
  20. package/dist/device/snapshot.js.map +1 -0
  21. package/dist/diagnostics.js +2218 -0
  22. package/dist/diagnostics.js.map +1 -0
  23. package/dist/homekit/adapter.js +2 -0
  24. package/dist/homekit/adapter.js.map +1 -0
  25. package/dist/homekit/adapters/battery.js +208 -0
  26. package/dist/homekit/adapters/battery.js.map +1 -0
  27. package/dist/homekit/adapters/camera-controls.js +527 -0
  28. package/dist/homekit/adapters/camera-controls.js.map +1 -0
  29. package/dist/homekit/adapters/camera-streaming.js +1515 -0
  30. package/dist/homekit/adapters/camera-streaming.js.map +1 -0
  31. package/dist/homekit/adapters/contact.js +138 -0
  32. package/dist/homekit/adapters/contact.js.map +1 -0
  33. package/dist/homekit/adapters/doorbell.js +57 -0
  34. package/dist/homekit/adapters/doorbell.js.map +1 -0
  35. package/dist/homekit/adapters/information.js +55 -0
  36. package/dist/homekit/adapters/information.js.map +1 -0
  37. package/dist/homekit/adapters/lock.js +489 -0
  38. package/dist/homekit/adapters/lock.js.map +1 -0
  39. package/dist/homekit/adapters/motion.js +100 -0
  40. package/dist/homekit/adapters/motion.js.map +1 -0
  41. package/dist/homekit/adapters/registry.js +26 -0
  42. package/dist/homekit/adapters/registry.js.map +1 -0
  43. package/dist/homekit/adapters/security-system.js +425 -0
  44. package/dist/homekit/adapters/security-system.js.map +1 -0
  45. package/dist/homekit/adapters/siren.js +136 -0
  46. package/dist/homekit/adapters/siren.js.map +1 -0
  47. package/dist/homekit/adapters/smart-light.js +662 -0
  48. package/dist/homekit/adapters/smart-light.js.map +1 -0
  49. package/dist/homekit/coverage-matrix.js +119 -0
  50. package/dist/homekit/coverage-matrix.js.map +1 -0
  51. package/dist/homekit/device-control.js +175 -0
  52. package/dist/homekit/device-control.js.map +1 -0
  53. package/dist/homekit/reconciler.js +319 -0
  54. package/dist/homekit/reconciler.js.map +1 -0
  55. package/dist/homekit/representation.js +23 -0
  56. package/dist/homekit/representation.js.map +1 -0
  57. package/dist/index.js +4 -9
  58. package/dist/index.js.map +1 -1
  59. package/dist/media/contracts.js +2 -0
  60. package/dist/media/contracts.js.map +1 -0
  61. package/dist/media/last-successful-image.js +208 -0
  62. package/dist/media/last-successful-image.js.map +1 -0
  63. package/dist/media/live-stream.js +1003 -0
  64. package/dist/media/live-stream.js.map +1 -0
  65. package/dist/media/recording.js +560 -0
  66. package/dist/media/recording.js.map +1 -0
  67. package/dist/media/session-budget.js +39 -0
  68. package/dist/media/session-budget.js.map +1 -0
  69. package/dist/media/snapshot.js +406 -0
  70. package/dist/media/snapshot.js.map +1 -0
  71. package/dist/media/station-live-sessions.js +101 -0
  72. package/dist/media/station-live-sessions.js.map +1 -0
  73. package/dist/platform.js +97 -769
  74. package/dist/platform.js.map +1 -1
  75. package/dist/runtime/channel.js +375 -0
  76. package/dist/runtime/channel.js.map +1 -0
  77. package/dist/runtime/owner.js +682 -0
  78. package/dist/runtime/owner.js.map +1 -0
  79. package/dist/runtime/sdk-client.js +211 -0
  80. package/dist/runtime/sdk-client.js.map +1 -0
  81. package/dist/runtime/tracker.js +211 -0
  82. package/dist/runtime/tracker.js.map +1 -0
  83. package/dist/settings.js +1 -43
  84. package/dist/settings.js.map +1 -1
  85. package/dist/storage.js +59 -0
  86. package/dist/storage.js.map +1 -0
  87. package/dist/ui/dashboard.js +181 -0
  88. package/dist/ui/dashboard.js.map +1 -0
  89. package/dist/ui/device-image.js +22 -0
  90. package/dist/ui/device-image.js.map +1 -0
  91. package/dist/ui/runtime-channel-client.js +305 -0
  92. package/dist/ui/runtime-channel-client.js.map +1 -0
  93. package/dist/ui/server.js +259 -0
  94. package/dist/ui/server.js.map +1 -0
  95. package/homebridge-ui/public/app.css +1902 -0
  96. package/homebridge-ui/public/assets/devices/clean/clean-T1250.webp +0 -0
  97. package/homebridge-ui/public/assets/devices/clean/clean-T2070.webp +0 -0
  98. package/homebridge-ui/public/assets/devices/clean/clean-T2080.webp +0 -0
  99. package/homebridge-ui/public/assets/devices/clean/clean-T2081.webp +0 -0
  100. package/homebridge-ui/public/assets/devices/clean/clean-T2103.webp +0 -0
  101. package/homebridge-ui/public/assets/devices/clean/clean-T2117.webp +0 -0
  102. package/homebridge-ui/public/assets/devices/clean/clean-T2118.webp +0 -0
  103. package/homebridge-ui/public/assets/devices/clean/clean-T2119.webp +0 -0
  104. package/homebridge-ui/public/assets/devices/clean/clean-T211A.webp +0 -0
  105. package/homebridge-ui/public/assets/devices/clean/clean-T2120.webp +0 -0
  106. package/homebridge-ui/public/assets/devices/clean/clean-T2123.webp +0 -0
  107. package/homebridge-ui/public/assets/devices/clean/clean-T2128.webp +0 -0
  108. package/homebridge-ui/public/assets/devices/clean/clean-T2130.webp +0 -0
  109. package/homebridge-ui/public/assets/devices/clean/clean-T2132.webp +0 -0
  110. package/homebridge-ui/public/assets/devices/clean/clean-T2150.webp +0 -0
  111. package/homebridge-ui/public/assets/devices/clean/clean-T2181.webp +0 -0
  112. package/homebridge-ui/public/assets/devices/clean/clean-T2182.webp +0 -0
  113. package/homebridge-ui/public/assets/devices/clean/clean-T2190.webp +0 -0
  114. package/homebridge-ui/public/assets/devices/clean/clean-T2192.webp +0 -0
  115. package/homebridge-ui/public/assets/devices/clean/clean-T2193.webp +0 -0
  116. package/homebridge-ui/public/assets/devices/clean/clean-T2194.webp +0 -0
  117. package/homebridge-ui/public/assets/devices/clean/clean-T2210.webp +0 -0
  118. package/homebridge-ui/public/assets/devices/clean/clean-T2250.webp +0 -0
  119. package/homebridge-ui/public/assets/devices/clean/clean-T2251.webp +0 -0
  120. package/homebridge-ui/public/assets/devices/clean/clean-T2252.webp +0 -0
  121. package/homebridge-ui/public/assets/devices/clean/clean-T2253.webp +0 -0
  122. package/homebridge-ui/public/assets/devices/clean/clean-T2254.webp +0 -0
  123. package/homebridge-ui/public/assets/devices/clean/clean-T2255.webp +0 -0
  124. package/homebridge-ui/public/assets/devices/clean/clean-T2256.webp +0 -0
  125. package/homebridge-ui/public/assets/devices/clean/clean-T2257.webp +0 -0
  126. package/homebridge-ui/public/assets/devices/clean/clean-T2258.webp +0 -0
  127. package/homebridge-ui/public/assets/devices/clean/clean-T2259.webp +0 -0
  128. package/homebridge-ui/public/assets/devices/clean/clean-T2261.webp +0 -0
  129. package/homebridge-ui/public/assets/devices/clean/clean-T2262.webp +0 -0
  130. package/homebridge-ui/public/assets/devices/clean/clean-T2266.webp +0 -0
  131. package/homebridge-ui/public/assets/devices/clean/clean-T2267.webp +0 -0
  132. package/homebridge-ui/public/assets/devices/clean/clean-T2268.webp +0 -0
  133. package/homebridge-ui/public/assets/devices/clean/clean-T2270.webp +0 -0
  134. package/homebridge-ui/public/assets/devices/clean/clean-T2272.webp +0 -0
  135. package/homebridge-ui/public/assets/devices/clean/clean-T2273.webp +0 -0
  136. package/homebridge-ui/public/assets/devices/clean/clean-T2276.webp +0 -0
  137. package/homebridge-ui/public/assets/devices/clean/clean-T2277.webp +0 -0
  138. package/homebridge-ui/public/assets/devices/clean/clean-T2278.webp +0 -0
  139. package/homebridge-ui/public/assets/devices/clean/clean-T2280.webp +0 -0
  140. package/homebridge-ui/public/assets/devices/clean/clean-T2292.webp +0 -0
  141. package/homebridge-ui/public/assets/devices/clean/clean-T2320.webp +0 -0
  142. package/homebridge-ui/public/assets/devices/clean/clean-T2351.webp +0 -0
  143. package/homebridge-ui/public/assets/devices/clean/clean-T2352.webp +0 -0
  144. package/homebridge-ui/public/assets/devices/clean/clean-T2353.webp +0 -0
  145. package/homebridge-ui/public/assets/devices/life/life-T8L00.webp +0 -0
  146. package/homebridge-ui/public/assets/devices/life/life-T8L01.webp +0 -0
  147. package/homebridge-ui/public/assets/devices/life/life-T8L02.webp +0 -0
  148. package/homebridge-ui/public/assets/devices/life/life-T8L04.webp +0 -0
  149. package/homebridge-ui/public/assets/devices/life/life-T8L10.webp +0 -0
  150. package/homebridge-ui/public/assets/devices/life/life-T8L20.webp +0 -0
  151. package/homebridge-ui/public/assets/devices/life/life-T8L30.webp +0 -0
  152. package/homebridge-ui/public/assets/devices/life/life-T8L40.webp +0 -0
  153. package/homebridge-ui/public/assets/devices/mower/mower-T2801.webp +0 -0
  154. package/homebridge-ui/public/assets/devices/mower/mower-T280B.webp +0 -0
  155. package/homebridge-ui/public/assets/devices/mower/mower-T2880.webp +0 -0
  156. package/homebridge-ui/public/assets/devices/security/security-T7400.webp +0 -0
  157. package/homebridge-ui/public/assets/devices/security/security-T7401.webp +0 -0
  158. package/homebridge-ui/public/assets/devices/security/security-T8001.webp +0 -0
  159. package/homebridge-ui/public/assets/devices/security/security-T8002.webp +0 -0
  160. package/homebridge-ui/public/assets/devices/security/security-T8010.webp +0 -0
  161. package/homebridge-ui/public/assets/devices/security/security-T8021.webp +0 -0
  162. package/homebridge-ui/public/assets/devices/security/security-T8023.webp +0 -0
  163. package/homebridge-ui/public/assets/devices/security/security-T8025.webp +0 -0
  164. package/homebridge-ui/public/assets/devices/security/security-T8030.webp +0 -0
  165. package/homebridge-ui/public/assets/devices/security/security-T8110.webp +0 -0
  166. package/homebridge-ui/public/assets/devices/security/security-T8111.webp +0 -0
  167. package/homebridge-ui/public/assets/devices/security/security-T8112.webp +0 -0
  168. package/homebridge-ui/public/assets/devices/security/security-T8113.webp +0 -0
  169. package/homebridge-ui/public/assets/devices/security/security-T8114.webp +0 -0
  170. package/homebridge-ui/public/assets/devices/security/security-T8122.webp +0 -0
  171. package/homebridge-ui/public/assets/devices/security/security-T8123.webp +0 -0
  172. package/homebridge-ui/public/assets/devices/security/security-T8124.webp +0 -0
  173. package/homebridge-ui/public/assets/devices/security/security-T8130.webp +0 -0
  174. package/homebridge-ui/public/assets/devices/security/security-T8131.webp +0 -0
  175. package/homebridge-ui/public/assets/devices/security/security-T8134.webp +0 -0
  176. package/homebridge-ui/public/assets/devices/security/security-T8140.webp +0 -0
  177. package/homebridge-ui/public/assets/devices/security/security-T8141.webp +0 -0
  178. package/homebridge-ui/public/assets/devices/security/security-T8150.webp +0 -0
  179. package/homebridge-ui/public/assets/devices/security/security-T8151.webp +0 -0
  180. package/homebridge-ui/public/assets/devices/security/security-T8152.webp +0 -0
  181. package/homebridge-ui/public/assets/devices/security/security-T8153.webp +0 -0
  182. package/homebridge-ui/public/assets/devices/security/security-T8160.webp +0 -0
  183. package/homebridge-ui/public/assets/devices/security/security-T8161.webp +0 -0
  184. package/homebridge-ui/public/assets/devices/security/security-T8162.webp +0 -0
  185. package/homebridge-ui/public/assets/devices/security/security-T8170.webp +0 -0
  186. package/homebridge-ui/public/assets/devices/security/security-T8171.webp +0 -0
  187. package/homebridge-ui/public/assets/devices/security/security-T8172.webp +0 -0
  188. package/homebridge-ui/public/assets/devices/security/security-T8173.webp +0 -0
  189. package/homebridge-ui/public/assets/devices/security/security-T8200.webp +0 -0
  190. package/homebridge-ui/public/assets/devices/security/security-T8200X.webp +0 -0
  191. package/homebridge-ui/public/assets/devices/security/security-T8201.webp +0 -0
  192. package/homebridge-ui/public/assets/devices/security/security-T8203.webp +0 -0
  193. package/homebridge-ui/public/assets/devices/security/security-T8210.webp +0 -0
  194. package/homebridge-ui/public/assets/devices/security/security-T8213.webp +0 -0
  195. package/homebridge-ui/public/assets/devices/security/security-T8214.webp +0 -0
  196. package/homebridge-ui/public/assets/devices/security/security-T8222.webp +0 -0
  197. package/homebridge-ui/public/assets/devices/security/security-T8400.webp +0 -0
  198. package/homebridge-ui/public/assets/devices/security/security-T8401.webp +0 -0
  199. package/homebridge-ui/public/assets/devices/security/security-T8410.webp +0 -0
  200. package/homebridge-ui/public/assets/devices/security/security-T8411.webp +0 -0
  201. package/homebridge-ui/public/assets/devices/security/security-T8414.webp +0 -0
  202. package/homebridge-ui/public/assets/devices/security/security-T8416.webp +0 -0
  203. package/homebridge-ui/public/assets/devices/security/security-T8417.webp +0 -0
  204. package/homebridge-ui/public/assets/devices/security/security-T8419.webp +0 -0
  205. package/homebridge-ui/public/assets/devices/security/security-T8419N.webp +0 -0
  206. package/homebridge-ui/public/assets/devices/security/security-T8420.webp +0 -0
  207. package/homebridge-ui/public/assets/devices/security/security-T8420X.webp +0 -0
  208. package/homebridge-ui/public/assets/devices/security/security-T8422.webp +0 -0
  209. package/homebridge-ui/public/assets/devices/security/security-T8423.webp +0 -0
  210. package/homebridge-ui/public/assets/devices/security/security-T8424.webp +0 -0
  211. package/homebridge-ui/public/assets/devices/security/security-T8425.webp +0 -0
  212. package/homebridge-ui/public/assets/devices/security/security-T8441.webp +0 -0
  213. package/homebridge-ui/public/assets/devices/security/security-T8442.webp +0 -0
  214. package/homebridge-ui/public/assets/devices/security/security-T8452.webp +0 -0
  215. package/homebridge-ui/public/assets/devices/security/security-T84A0.webp +0 -0
  216. package/homebridge-ui/public/assets/devices/security/security-T84A1.webp +0 -0
  217. package/homebridge-ui/public/assets/devices/security/security-T8500.webp +0 -0
  218. package/homebridge-ui/public/assets/devices/security/security-T8502.webp +0 -0
  219. package/homebridge-ui/public/assets/devices/security/security-T8503.webp +0 -0
  220. package/homebridge-ui/public/assets/devices/security/security-T8504.webp +0 -0
  221. package/homebridge-ui/public/assets/devices/security/security-T8506.webp +0 -0
  222. package/homebridge-ui/public/assets/devices/security/security-T8510.webp +0 -0
  223. package/homebridge-ui/public/assets/devices/security/security-T8510P.webp +0 -0
  224. package/homebridge-ui/public/assets/devices/security/security-T8520.webp +0 -0
  225. package/homebridge-ui/public/assets/devices/security/security-T8520P.webp +0 -0
  226. package/homebridge-ui/public/assets/devices/security/security-T8530.webp +0 -0
  227. package/homebridge-ui/public/assets/devices/security/security-T8531.webp +0 -0
  228. package/homebridge-ui/public/assets/devices/security/security-T85D0.webp +0 -0
  229. package/homebridge-ui/public/assets/devices/security/security-T85L0.webp +0 -0
  230. package/homebridge-ui/public/assets/devices/security/security-T85P0.webp +0 -0
  231. package/homebridge-ui/public/assets/devices/security/security-T85V0.webp +0 -0
  232. package/homebridge-ui/public/assets/devices/security/security-T8600.webp +0 -0
  233. package/homebridge-ui/public/assets/devices/security/security-T86P2.webp +0 -0
  234. package/homebridge-ui/public/assets/devices/security/security-T8790.webp +0 -0
  235. package/homebridge-ui/public/assets/devices/security/security-T87B0.webp +0 -0
  236. package/homebridge-ui/public/assets/devices/security/security-T87B2.webp +0 -0
  237. package/homebridge-ui/public/assets/devices/security/security-T8900.webp +0 -0
  238. package/homebridge-ui/public/assets/devices/security/security-T8910.webp +0 -0
  239. package/homebridge-ui/public/assets/devices/security/security-T8920.webp +0 -0
  240. package/homebridge-ui/public/assets/devices/security/security-T8960.webp +0 -0
  241. package/homebridge-ui/public/assets/devices/security/security-T8B0.webp +0 -0
  242. package/homebridge-ui/public/assets/devices/security/security-T8E00.webp +0 -0
  243. package/homebridge-ui/public/assets/devices/security/security-T8N00.webp +0 -0
  244. package/homebridge-ui/public/assets/devices/security/security-T8W11C.webp +0 -0
  245. package/homebridge-ui/public/assets/devices/security/security-T90F0.webp +0 -0
  246. package/homebridge-ui/public/assets/devices/security/security-T90M0.webp +0 -0
  247. package/homebridge-ui/public/assets/devices/security/security-T90R0.webp +0 -0
  248. package/homebridge-ui/public/assets/devices/security/security-T90S0.webp +0 -0
  249. package/homebridge-ui/public/assets/icons/inventory.svg +1 -1
  250. package/homebridge-ui/public/assets/icons/troubleshoot.svg +1 -0
  251. package/homebridge-ui/public/assets/icons/warning.svg +1 -1
  252. package/homebridge-ui/public/assets/logo-dark.svg +10 -0
  253. package/homebridge-ui/public/assets/logo.svg +10 -0
  254. package/homebridge-ui/public/i18n/en.json +218 -0
  255. package/homebridge-ui/public/i18n/fr.json +218 -0
  256. package/homebridge-ui/public/index.html +377 -28
  257. package/homebridge-ui/public/js/app.js +1065 -0
  258. package/homebridge-ui/public/js/dashboard.js +385 -0
  259. package/homebridge-ui/public/js/legacy-settings.js +37 -0
  260. package/homebridge-ui/public/js/profile-wizard.js +68 -0
  261. package/homebridge-ui/server.js +2 -1393
  262. package/i18n/runtime/en.json +100 -0
  263. package/media/Snapshot-Unavailable.jpg +0 -0
  264. package/media/Snapshot-black.jpg +0 -0
  265. package/media/camera-disabled.jpg +0 -0
  266. package/media/camera-offline.jpg +0 -0
  267. package/package.json +29 -24
  268. package/.claude/CLAUDE.md +0 -175
  269. package/.claude/PRD.md +0 -241
  270. package/.claude/docs/debug-recording-lessons.md +0 -91
  271. package/.claude/docs/hksv-recording-fix.md +0 -160
  272. package/.claude/skills/architect/SKILL.md +0 -76
  273. package/.claude/skills/developer/SKILL.md +0 -59
  274. package/.claude/skills/new-device-support/SKILL.md +0 -212
  275. package/.claude/skills/new-device-support/check-device.mjs +0 -363
  276. package/.claude/skills/new-device-support/map-properties.mjs +0 -450
  277. package/.claude/skills/new-device-support/verify-device.mjs +0 -272
  278. package/.claude/skills/planner/SKILL.md +0 -100
  279. package/.claude/skills/qa/SKILL.md +0 -79
  280. package/.claude/skills/support/SKILL.md +0 -175
  281. package/.prettierignore +0 -7
  282. package/.prettierrc.json +0 -7
  283. package/FUNDING.yml +0 -1
  284. package/dist/accessories/AutoSyncStationAccessory.js +0 -157
  285. package/dist/accessories/AutoSyncStationAccessory.js.map +0 -1
  286. package/dist/accessories/BaseAccessory.js +0 -261
  287. package/dist/accessories/BaseAccessory.js.map +0 -1
  288. package/dist/accessories/CameraAccessory.js +0 -431
  289. package/dist/accessories/CameraAccessory.js.map +0 -1
  290. package/dist/accessories/Device.js +0 -65
  291. package/dist/accessories/Device.js.map +0 -1
  292. package/dist/accessories/EntrySensorAccessory.js +0 -48
  293. package/dist/accessories/EntrySensorAccessory.js.map +0 -1
  294. package/dist/accessories/LockAccessory.js +0 -142
  295. package/dist/accessories/LockAccessory.js.map +0 -1
  296. package/dist/accessories/MotionSensorAccessory.js +0 -48
  297. package/dist/accessories/MotionSensorAccessory.js.map +0 -1
  298. package/dist/accessories/SmartDropAccessory.js +0 -141
  299. package/dist/accessories/SmartDropAccessory.js.map +0 -1
  300. package/dist/accessories/StationAccessory.js +0 -373
  301. package/dist/accessories/StationAccessory.js.map +0 -1
  302. package/dist/config.js +0 -26
  303. package/dist/config.js.map +0 -1
  304. package/dist/controller/DebugRecordingManager.js +0 -402
  305. package/dist/controller/DebugRecordingManager.js.map +0 -1
  306. package/dist/controller/LocalLivestreamManager.js +0 -371
  307. package/dist/controller/LocalLivestreamManager.js.map +0 -1
  308. package/dist/controller/recordingDelegate.js +0 -252
  309. package/dist/controller/recordingDelegate.js.map +0 -1
  310. package/dist/controller/snapshotDelegate.js +0 -348
  311. package/dist/controller/snapshotDelegate.js.map +0 -1
  312. package/dist/controller/streamingDelegate.js +0 -389
  313. package/dist/controller/streamingDelegate.js.map +0 -1
  314. package/dist/interfaces.js +0 -2
  315. package/dist/interfaces.js.map +0 -1
  316. package/dist/media/Snapshot-Unavailable.png +0 -0
  317. package/dist/media/Snapshot-Unavailable.xcf +0 -0
  318. package/dist/media/Snapshot-black.png +0 -0
  319. package/dist/media/camera-disabled.png +0 -0
  320. package/dist/media/camera-offline.png +0 -0
  321. package/dist/media/media/Snapshot-Unavailable.png +0 -0
  322. package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
  323. package/dist/media/media/Snapshot-black.png +0 -0
  324. package/dist/media/media/camera-disabled.png +0 -0
  325. package/dist/media/media/camera-offline.png +0 -0
  326. package/dist/utils/Talkback.js +0 -162
  327. package/dist/utils/Talkback.js.map +0 -1
  328. package/dist/utils/accessoriesStore.js +0 -207
  329. package/dist/utils/accessoriesStore.js.map +0 -1
  330. package/dist/utils/configTypes.js +0 -36
  331. package/dist/utils/configTypes.js.map +0 -1
  332. package/dist/utils/ffmpeg.js +0 -1111
  333. package/dist/utils/ffmpeg.js.map +0 -1
  334. package/dist/utils/interfaces.js +0 -8
  335. package/dist/utils/interfaces.js.map +0 -1
  336. package/dist/utils/utils.js +0 -224
  337. package/dist/utils/utils.js.map +0 -1
  338. package/dist/version.js +0 -2
  339. package/dist/version.js.map +0 -1
  340. package/homebridge-eufy-security.png +0 -0
  341. package/homebridge-ui/diagnosticsCrypto.js +0 -112
  342. package/homebridge-ui/public/app.js +0 -237
  343. package/homebridge-ui/public/assets/devices/4g_lte_cam_s330_large.png +0 -0
  344. package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
  345. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
  346. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
  347. package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
  348. package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
  349. package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
  350. package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
  351. package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
  352. package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
  353. package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
  354. package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
  355. package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
  356. package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
  357. package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
  358. package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
  359. package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
  360. package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
  361. package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
  362. package/homebridge-ui/public/assets/devices/flood_freeze_sensor_e20_t90F0_large.png +0 -0
  363. package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
  364. package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
  365. package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
  366. package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
  367. package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.png +0 -0
  368. package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
  369. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  370. package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
  371. package/homebridge-ui/public/assets/devices/homebasemini_large.png +0 -0
  372. package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
  373. package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
  374. package/homebridge-ui/public/assets/devices/indoorcamC220_t8419N_large.png +0 -0
  375. package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
  376. package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
  377. package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
  378. package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
  379. package/homebridge-ui/public/assets/devices/indoorcams350_large.png +0 -0
  380. package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
  381. package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
  382. package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
  383. package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
  384. package/homebridge-ui/public/assets/devices/pir_sensor_e20_large.png +0 -0
  385. package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
  386. package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
  387. package/homebridge-ui/public/assets/devices/siren_sensor_e20_large.png +0 -0
  388. package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
  389. package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
  390. package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
  391. package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
  392. package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
  393. package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
  394. package/homebridge-ui/public/assets/devices/smartlock_t85D0_large.png +0 -0
  395. package/homebridge-ui/public/assets/devices/smartlock_t85L0_large.png +0 -0
  396. package/homebridge-ui/public/assets/devices/smartlock_t85P0_large.png +0 -0
  397. package/homebridge-ui/public/assets/devices/smartlock_t85V0_large.png +0 -0
  398. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
  399. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
  400. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
  401. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
  402. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
  403. package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
  404. package/homebridge-ui/public/assets/devices/smartlock_video_t8531_large.png +0 -0
  405. package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
  406. package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
  407. package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
  408. package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
  409. package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
  410. package/homebridge-ui/public/assets/devices/smoke_alarm_e10_t90S0_large.png +0 -0
  411. package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
  412. package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
  413. package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
  414. package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
  415. package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
  416. package/homebridge-ui/public/assets/devices/solocame42_large.png +0 -0
  417. package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
  418. package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
  419. package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
  420. package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
  421. package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
  422. package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
  423. package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
  424. package/homebridge-ui/public/assets/devices/unknown.png +0 -0
  425. package/homebridge-ui/public/assets/devices/walllight_s100_large.png +0 -0
  426. package/homebridge-ui/public/assets/devices/walllight_s120_large.png +0 -0
  427. package/homebridge-ui/public/assets/devices/water_freeze_sensor_t8920_large.png +0 -0
  428. package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
  429. package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
  430. package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
  431. package/homebridge-ui/public/assets/icons/attach.svg +0 -1
  432. package/homebridge-ui/public/assets/icons/battery_0.svg +0 -1
  433. package/homebridge-ui/public/assets/icons/battery_1.svg +0 -1
  434. package/homebridge-ui/public/assets/icons/battery_2.svg +0 -1
  435. package/homebridge-ui/public/assets/icons/battery_3.svg +0 -1
  436. package/homebridge-ui/public/assets/icons/battery_4.svg +0 -1
  437. package/homebridge-ui/public/assets/icons/battery_5.svg +0 -1
  438. package/homebridge-ui/public/assets/icons/battery_6.svg +0 -1
  439. package/homebridge-ui/public/assets/icons/copy.svg +0 -1
  440. package/homebridge-ui/public/assets/icons/delete.svg +0 -1
  441. package/homebridge-ui/public/assets/icons/download.svg +0 -1
  442. package/homebridge-ui/public/assets/icons/info.svg +0 -1
  443. package/homebridge-ui/public/assets/icons/refresh.svg +0 -1
  444. package/homebridge-ui/public/assets/icons/satellite_alt.svg +0 -1
  445. package/homebridge-ui/public/assets/icons/solar_power.svg +0 -1
  446. package/homebridge-ui/public/components/device-card.js +0 -174
  447. package/homebridge-ui/public/components/guard-modes.js +0 -88
  448. package/homebridge-ui/public/components/number-input.js +0 -121
  449. package/homebridge-ui/public/components/select.js +0 -73
  450. package/homebridge-ui/public/components/toggle.js +0 -68
  451. package/homebridge-ui/public/services/api.js +0 -250
  452. package/homebridge-ui/public/services/config.js +0 -144
  453. package/homebridge-ui/public/style.css +0 -818
  454. package/homebridge-ui/public/utils/countries.js +0 -73
  455. package/homebridge-ui/public/utils/device-images.js +0 -102
  456. package/homebridge-ui/public/utils/helpers.js +0 -115
  457. package/homebridge-ui/public/views/dashboard.js +0 -226
  458. package/homebridge-ui/public/views/device-detail.js +0 -626
  459. package/homebridge-ui/public/views/diagnostics.js +0 -402
  460. package/homebridge-ui/public/views/login.js +0 -650
  461. package/homebridge-ui/public/views/recordings.js +0 -319
  462. package/homebridge-ui/public/views/settings.js +0 -197
  463. package/homebridge-ui/public/views/unsupported-detail.js +0 -296
  464. package/media/Snapshot-Unavailable.png +0 -0
  465. package/media/Snapshot-Unavailable.xcf +0 -0
  466. package/media/Snapshot-black.png +0 -0
  467. package/media/camera-disabled.png +0 -0
  468. package/media/camera-offline.png +0 -0
  469. package/scripts/decrypt-diagnostics.mjs +0 -257
@@ -1,296 +0,0 @@
1
- /**
2
- * Unsupported Device Detail View — shows device info dump and guided CTA
3
- * for requesting support via GitHub.
4
- */
5
- // eslint-disable-next-line no-unused-vars
6
- const UnsupportedDetailView = {
7
-
8
- _container: null,
9
-
10
- /**
11
- * @param {HTMLElement} container
12
- * @param {string} id - uniqueId / serial number
13
- */
14
- async render(container, id) {
15
- this._container = container;
16
- container.innerHTML = '';
17
-
18
- const accessory = this._findAccessory(id);
19
-
20
- if (!accessory) {
21
- container.innerHTML = `
22
- <div class="text-center text-muted py-5">
23
- <p>Device not found.</p>
24
- <button class="btn btn-outline-secondary btn-sm" id="btn-back">Back to Dashboard</button>
25
- </div>`;
26
- container.querySelector('#btn-back').addEventListener('click', () => App.navigate('dashboard'));
27
- return;
28
- }
29
-
30
- // Header (no image for unsupported)
31
- this._renderHeader(container, accessory);
32
-
33
- // Main content
34
- const content = document.createElement('div');
35
- await this._renderDetail(content, accessory);
36
- container.appendChild(content);
37
- },
38
-
39
- // ===== Header =====
40
- _renderHeader(container, accessory) {
41
- const header = document.createElement('div');
42
- header.className = 'detail-header';
43
-
44
- const backBtn = document.createElement('button');
45
- backBtn.className = 'btn btn-link p-0';
46
- backBtn.innerHTML = '← Back';
47
- backBtn.style.textDecoration = 'none';
48
- backBtn.addEventListener('click', () => App.navigate('dashboard'));
49
-
50
- const info = document.createElement('div');
51
- info.className = 'detail-header__info';
52
- info.innerHTML = `
53
- <h5>${Helpers.escHtml(accessory.displayName)}</h5>
54
- <small>${accessory.typename || ('Type ' + accessory.type)} · ${accessory.uniqueId}</small>
55
- `;
56
-
57
- header.appendChild(backBtn);
58
- header.appendChild(info);
59
- container.appendChild(header);
60
- },
61
-
62
- // ===== Detail Content =====
63
- async _renderDetail(content, accessory) {
64
- const REPO = 'homebridge-plugins/homebridge-eufy-security';
65
- const LABEL = 'device-support';
66
- const COMPAT_URL = 'https://bropat.github.io/eufy-security-client/#/supported_devices';
67
-
68
- const section = document.createElement('div');
69
- section.className = 'detail-section unsupported-detail';
70
-
71
- // Description
72
- const desc = document.createElement('p');
73
- desc.className = 'text-muted';
74
- desc.innerHTML =
75
- '<strong>' + Helpers.iconHtml('info.svg') + ' This device was detected but is not yet supported.</strong><br />' +
76
- 'New device support must first be added to the eufy-security-client library. ' +
77
- 'If your device is not on the compatibility list, please <strong>search for existing issues first</strong> before opening a new one.';
78
- section.appendChild(desc);
79
-
80
- // Device info dump — all available data in one box
81
- const props = accessory.properties || {};
82
-
83
- // Load raw intel from unsupported.json (served via /unsupportedDevices endpoint)
84
- let unsupportedIntel = null;
85
- try {
86
- const unsupportedData = await Api.loadUnsupportedDevices();
87
- const entries = unsupportedData.devices || [];
88
- unsupportedIntel = entries.find(e => e.uniqueId === accessory.uniqueId) || null;
89
- } catch (e) {
90
- // ignore — will fall back to basic properties
91
- }
92
-
93
- const deviceInfo = {
94
- uniqueId: accessory.uniqueId,
95
- displayName: accessory.displayName,
96
- type: accessory.type,
97
- typename: accessory.typename || undefined,
98
- ...props,
99
- ...(unsupportedIntel ? {
100
- rawDevice: unsupportedIntel.rawDevice,
101
- rawProperties: unsupportedIntel.rawProperties,
102
- } : {}),
103
- };
104
- // Remove potentially large/sensitive fields
105
- delete deviceInfo.picture;
106
-
107
- // CTA stepper + buttons
108
- const stepsWrap = document.createElement('div');
109
- stepsWrap.className = 'unsupported-detail__steps';
110
-
111
- // Stepper frieze
112
- const stepper = document.createElement('div');
113
- stepper.className = 'unsupported-stepper';
114
- const steps = [
115
- { num: '1', label: 'Check', color: 'success' },
116
- { num: '2', label: 'Search', color: 'primary' },
117
- { num: '3', label: 'Copy', color: 'warning' },
118
- { num: '4', label: 'Create', color: 'danger' },
119
- ];
120
- steps.forEach((step, i) => {
121
- const stepEl = document.createElement('div');
122
- stepEl.className = 'unsupported-stepper__step';
123
- stepEl.innerHTML = `<span class="unsupported-stepper__circle unsupported-stepper__circle--${step.color}">${step.num}</span><span class="unsupported-stepper__label">${step.label}</span>`;
124
- stepper.appendChild(stepEl);
125
- if (i < steps.length - 1) {
126
- const line = document.createElement('div');
127
- line.className = 'unsupported-stepper__line';
128
- stepper.appendChild(line);
129
- }
130
- });
131
- stepsWrap.appendChild(stepper);
132
-
133
- // Buttons row
134
- const btnGroup = document.createElement('div');
135
- btnGroup.className = 'unsupported-detail__actions';
136
-
137
- // 1) Check compatibility list
138
- const compatBtn = document.createElement('a');
139
- compatBtn.href = COMPAT_URL;
140
- compatBtn.target = '_blank';
141
- compatBtn.rel = 'noopener noreferrer';
142
- compatBtn.className = 'btn btn-success';
143
- compatBtn.textContent = 'Check Compatibility ↗';
144
- btnGroup.appendChild(compatBtn);
145
-
146
- // 2) Search existing issues with label
147
- const searchQuery = encodeURIComponent(`is:issue label:${LABEL} ${accessory.type}`);
148
- const searchBtn = document.createElement('a');
149
- searchBtn.href = `https://github.com/${REPO}/issues?q=${searchQuery}`;
150
- searchBtn.target = '_blank';
151
- searchBtn.rel = 'noopener noreferrer';
152
- searchBtn.className = 'btn btn-outline-primary';
153
- searchBtn.textContent = 'Search Existing Issues ↗';
154
- btnGroup.appendChild(searchBtn);
155
-
156
- // 3) Copy device JSON to clipboard
157
- const deviceDump = JSON.stringify(deviceInfo, null, 2);
158
- const copyJsonBtn = document.createElement('button');
159
- copyJsonBtn.className = 'btn btn-outline-warning';
160
- copyJsonBtn.textContent = 'Copy Device Info';
161
- copyJsonBtn.addEventListener('click', () => {
162
- this._copyToClipboard(deviceDump).then(() => {
163
- copyJsonBtn.textContent = '✓ Copied!';
164
- setTimeout(() => { copyJsonBtn.textContent = 'Copy Device Info'; }, 2000);
165
- });
166
- });
167
- btnGroup.appendChild(copyJsonBtn);
168
-
169
- // 4) Create new issue (without embedding JSON — user pastes it)
170
- const model = props.model || accessory.type;
171
- const issueTitle = encodeURIComponent(`[Device Support] ${model} (Type ${accessory.type})`);
172
- const templateParams = [
173
- `template=device_support.yml`,
174
- `title=${issueTitle}`,
175
- `labels=${LABEL}`,
176
- ].join('&');
177
- const createBtn = document.createElement('a');
178
- createBtn.href = `https://github.com/${REPO}/issues/new?${templateParams}`;
179
- createBtn.target = '_blank';
180
- createBtn.rel = 'noopener noreferrer';
181
- createBtn.className = 'btn btn-outline-danger';
182
- createBtn.textContent = 'Create an Issue ↗';
183
- btnGroup.appendChild(createBtn);
184
-
185
- stepsWrap.appendChild(btnGroup);
186
-
187
- // Paste reminder note
188
- const pasteNote = document.createElement('p');
189
- pasteNote.className = 'unsupported-detail__paste-note';
190
- pasteNote.innerHTML = Helpers.iconHtml('info.svg', 14) +
191
- ' Copy the <strong>Device Information</strong> below first, then paste it into the issue form on GitHub.';
192
- stepsWrap.appendChild(pasteNote);
193
-
194
- section.appendChild(stepsWrap);
195
-
196
- // External links note
197
- const extNote = document.createElement('p');
198
- extNote.className = 'text-muted';
199
- extNote.style.cssText = 'font-size: 0.75rem; text-align: right;';
200
- extNote.textContent = '↗ These links open in a new browser tab on GitHub.';
201
- section.appendChild(extNote);
202
-
203
- const infoTitle = document.createElement('div');
204
- infoTitle.className = 'detail-section__title';
205
- infoTitle.textContent = 'Device Information';
206
- section.appendChild(infoTitle);
207
-
208
- const infoNote = document.createElement('p');
209
- infoNote.className = 'text-muted';
210
- infoNote.style.cssText = 'font-size: 0.75rem;';
211
- infoNote.textContent = 'Diagnostic information below includes raw device properties from the client library.';
212
- section.appendChild(infoNote);
213
-
214
- // JSON dump with copy button overlay
215
- const dumpWrap = document.createElement('div');
216
- dumpWrap.className = 'unsupported-detail__dump-wrap';
217
-
218
- const copyBtn = document.createElement('button');
219
- copyBtn.className = 'unsupported-detail__copy-btn';
220
-
221
- const _setCopyLabel = (text) => {
222
- copyBtn.innerHTML = '';
223
- copyBtn.appendChild(Helpers.icon('copy.svg'));
224
- copyBtn.append(' ' + text);
225
- };
226
- _setCopyLabel('Copy');
227
-
228
- copyBtn.addEventListener('click', () => {
229
- this._copyToClipboard(pre.textContent).then(() => {
230
- copyBtn.textContent = '✓ Copied!';
231
- setTimeout(() => { _setCopyLabel('Copy'); }, 2000);
232
- });
233
- });
234
-
235
- const pre = document.createElement('pre');
236
- pre.className = 'unsupported-detail__dump';
237
- pre.textContent = JSON.stringify(deviceInfo, null, 2);
238
-
239
- dumpWrap.appendChild(copyBtn);
240
- dumpWrap.appendChild(pre);
241
- section.appendChild(dumpWrap);
242
-
243
- content.appendChild(section);
244
- },
245
-
246
- // ===== Helpers =====
247
-
248
- /**
249
- * Copy text to clipboard with fallback for non-secure contexts (e.g. Homebridge iframe).
250
- * @param {string} text
251
- * @returns {Promise<void>}
252
- */
253
- _copyToClipboard(text) {
254
- if (navigator.clipboard && navigator.clipboard.writeText) {
255
- return navigator.clipboard.writeText(text).catch(() => this._copyFallback(text));
256
- }
257
- return this._copyFallback(text);
258
- },
259
-
260
- _copyFallback(text) {
261
- return new Promise((resolve, reject) => {
262
- const textarea = document.createElement('textarea');
263
- textarea.value = text;
264
- textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
265
- document.body.appendChild(textarea);
266
- textarea.select();
267
- try {
268
- document.execCommand('copy') ? resolve() : reject(new Error('execCommand failed'));
269
- } catch (err) {
270
- reject(err);
271
- } finally {
272
- document.body.removeChild(textarea);
273
- }
274
- });
275
- },
276
-
277
- /**
278
- * Find an accessory (device or station) by uniqueId across all stations.
279
- * For standalone devices the station and device share the same uniqueId —
280
- * prefer the device because it carries richer data.
281
- * @param {string} id
282
- * @returns {object|null}
283
- */
284
- _findAccessory(id) {
285
- const stations = App.state.stations || [];
286
- for (const s of stations) {
287
- // Check devices first — standalone devices have the same uniqueId as their station
288
- // but carry richer data
289
- for (const d of s.devices || []) {
290
- if (d.uniqueId === id) return d;
291
- }
292
- if (s.uniqueId === id) return s;
293
- }
294
- return null;
295
- },
296
- };
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,257 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Decrypt and extract an encrypted diagnostics archive produced by the plugin.
5
- *
6
- * Usage:
7
- * node decrypt-diagnostics.mjs <encrypted-file.tar.gz> [private-key.pem]
8
- *
9
- * If the private key is omitted, the script looks for it in the keys/
10
- * directory next to itself (keys/diagnostics_private.pem).
11
- *
12
- * The archive is decrypted and extracted into a folder next to the input file,
13
- * named after the archive (e.g. diagnostics-2026-03-02-11-25-31/).
14
- *
15
- * File format (the .tar.gz is actually encrypted, not a plain gzip):
16
- * [4 bytes] – magic: "DIAG"
17
- * [1 byte] – format version (currently 0x01)
18
- * [8 bytes] – creation timestamp (BigUInt64BE, ms since Unix epoch)
19
- * [2 bytes] – big-endian uint16: length of the encrypted AES key
20
- * [N bytes] – RSA-OAEP encrypted AES-256 key
21
- * [12 bytes] – AES-GCM initialisation vector
22
- * [16 bytes] – AES-GCM authentication tag
23
- * [rest] – AES-256-GCM ciphertext
24
- *
25
- * AAD (Additional Authenticated Data) covers bytes 0 through the end of the
26
- * encrypted AES key. GCM verifies this automatically during decryption.
27
- */
28
-
29
- import crypto from 'node:crypto';
30
- import fs from 'node:fs';
31
- import path from 'node:path';
32
- import { execFileSync, spawnSync } from 'node:child_process';
33
- import { fileURLToPath } from 'node:url';
34
-
35
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
- const DEFAULT_KEY = path.join(__dirname, '..', 'keys', 'diagnostics_private.pem');
37
-
38
- /** Maximum allowed .enc file size (140 MB). */
39
- const MAX_ENC_SIZE = 140 * 1024 * 1024;
40
-
41
- /** Maximum number of entries allowed in the archive. */
42
- const MAX_ENTRIES = 100;
43
-
44
- /** Only these file extensions are expected in a legitimate diagnostics archive. */
45
- const ALLOWED_EXTENSIONS = new Set(['.log', '.gz', '.json']);
46
-
47
- /** Archives older than this are flagged with a warning (90 days). */
48
- const EXPIRY_MS = 90 * 24 * 60 * 60 * 1000;
49
-
50
- function main() {
51
- const args = process.argv.slice(2);
52
-
53
- if (args.length < 1) {
54
- console.error('Usage: node decrypt-diagnostics.mjs <encrypted-file.tar.gz> [private-key.pem]');
55
- process.exit(1);
56
- }
57
-
58
- const encFile = path.resolve(args[0]);
59
- let keyFile = args[1] ? path.resolve(args[1]) : DEFAULT_KEY;
60
-
61
- if (!fs.existsSync(keyFile)) {
62
- console.error(`Private key not found: ${keyFile}`);
63
- process.exit(1);
64
- }
65
-
66
- // Read inputs
67
- const encBuffer = fs.readFileSync(encFile);
68
-
69
- // Reject oversized files
70
- if (encBuffer.length > MAX_ENC_SIZE) {
71
- console.error(`File too large (${(encBuffer.length / 1024 / 1024).toFixed(1)} MB) — max allowed is ${MAX_ENC_SIZE / 1024 / 1024} MB.`);
72
- process.exit(1);
73
- }
74
-
75
- const privateKey = fs.readFileSync(keyFile, 'utf-8');
76
-
77
- // Validate minimum size: 4 (magic) + 1 (version) + 8 (timestamp) + 2 (keyLen) + 512 (RSA-4096 encrypted key) + 12 (IV) + 16 (tag) + 1 (min ciphertext)
78
- const MIN_SIZE = 4 + 1 + 8 + 2 + 512 + 12 + 16 + 1;
79
- if (encBuffer.length < MIN_SIZE) {
80
- console.error(`File too small (${encBuffer.length} bytes) — not a valid encrypted diagnostics archive.`);
81
- process.exit(1);
82
- }
83
-
84
- // Verify magic header and version
85
- let offset = 0;
86
-
87
- const magic = encBuffer.subarray(offset, offset + 4).toString();
88
- offset += 4;
89
- if (magic !== 'DIAG') {
90
- console.error(`Invalid file — expected magic "DIAG", got "${magic}".`);
91
- process.exit(1);
92
- }
93
-
94
- const version = encBuffer[offset];
95
- offset += 1;
96
- if (version !== 0x01) {
97
- console.error(`Unsupported format version ${version} — this script supports version 1.`);
98
- process.exit(1);
99
- }
100
-
101
- // Parse the envelope
102
- const createdAtMs = Number(encBuffer.readBigUInt64BE(offset));
103
- offset += 8;
104
- const createdAt = new Date(createdAtMs);
105
-
106
- if (Number.isNaN(createdAt.getTime())) {
107
- console.error('Invalid timestamp in archive header — file may be corrupted or tampered.');
108
- process.exit(1);
109
- }
110
-
111
- console.log(`Archive created: ${createdAt.toISOString()}`);
112
-
113
- const ageMs = Date.now() - createdAtMs;
114
- if (ageMs > EXPIRY_MS) {
115
- const ageDays = Math.round(ageMs / (24 * 60 * 60 * 1000));
116
- console.warn(`⚠️ WARNING: This archive is ${ageDays} days old (created ${createdAt.toISOString()}).`);
117
- }
118
- if (createdAtMs > Date.now() + 24 * 60 * 60 * 1000) {
119
- console.warn('⚠️ WARNING: Archive timestamp is in the future — clock skew or tampering?');
120
- }
121
-
122
- const keyLen = encBuffer.readUInt16BE(offset);
123
- offset += 2;
124
-
125
- if (offset + keyLen + 12 + 16 > encBuffer.length) {
126
- console.error('Malformed archive — encrypted key length exceeds file size.');
127
- process.exit(1);
128
- }
129
-
130
- const encryptedKey = encBuffer.subarray(offset, offset + keyLen);
131
- offset += keyLen;
132
-
133
- // The AAD covers everything from byte 0 through the end of encryptedKey
134
- // (magic + version + timestamp + keyLen + encryptedKey)
135
- const aadEnd = 4 + 1 + 8 + 2 + keyLen;
136
- const aad = encBuffer.subarray(0, aadEnd);
137
-
138
- const iv = encBuffer.subarray(offset, offset + 12);
139
- offset += 12;
140
-
141
- const authTag = encBuffer.subarray(offset, offset + 16);
142
- offset += 16;
143
-
144
- const ciphertext = encBuffer.subarray(offset);
145
-
146
- // Unwrap the AES key with the private RSA key
147
- let aesKey;
148
- try {
149
- aesKey = crypto.privateDecrypt(
150
- {
151
- key: privateKey,
152
- padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
153
- oaepHash: 'sha256',
154
- },
155
- encryptedKey,
156
- );
157
- } catch (err) {
158
- console.error('Failed to decrypt AES key — wrong private key?', err.message);
159
- process.exit(2);
160
- }
161
-
162
- // Decrypt the payload
163
- const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
164
- aesKey.fill(0); // scrub key from memory
165
- decipher.setAAD(aad);
166
- decipher.setAuthTag(authTag);
167
-
168
- let decrypted;
169
- try {
170
- decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
171
- } catch (err) {
172
- console.error('Decryption failed — data may be corrupted.', err.message);
173
- process.exit(3);
174
- }
175
-
176
- // Determine output directory next to the .enc file
177
- const encDir = path.dirname(encFile);
178
- const baseName = path.basename(encFile)
179
- .replace(/\.tar\.gz(?:\.enc)?$/, '');
180
- const outDir = path.join(encDir, baseName);
181
-
182
- // --- Safety: inspect tar contents before extracting (piped via stdin, no temp file) ---
183
- const listResult = spawnSync('tar', ['tzf', '-'], { input: decrypted, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
184
- if (listResult.status !== 0) {
185
- console.error('Failed to list archive contents:', listResult.stderr?.trim() || 'unknown error');
186
- process.exit(4);
187
- }
188
-
189
- const listing = listResult.stdout.trim().split('\n');
190
-
191
- // Reject archives with too many entries (tar bomb protection)
192
- if (listing.length > MAX_ENTRIES) {
193
- console.error(`BLOCKED: archive contains ${listing.length} entries (max ${MAX_ENTRIES}).`);
194
- console.error('This file may be a tar bomb. Aborting.');
195
- process.exit(5);
196
- }
197
-
198
- for (const entry of listing) {
199
- const normalised = path.normalize(entry);
200
-
201
- // Path traversal / absolute path check
202
- if (path.isAbsolute(normalised) || normalised.startsWith('..')) {
203
- console.error(`BLOCKED: archive contains path-traversal entry: "${entry}"`);
204
- console.error('This file may be maliciously crafted. Aborting.');
205
- process.exit(5);
206
- }
207
-
208
- // Extension allowlist (skip directory entries ending with /)
209
- if (!entry.endsWith('/')) {
210
- const ext = path.extname(entry).toLowerCase();
211
- // Handle compound extensions like .log.gz
212
- const doubleExt = path.extname(entry.slice(0, -ext.length)).toLowerCase() + ext;
213
- if (!ALLOWED_EXTENSIONS.has(ext) && !ALLOWED_EXTENSIONS.has(doubleExt)) {
214
- console.error(`BLOCKED: archive contains file with disallowed extension: "${entry}"`);
215
- console.error(`Only ${[...ALLOWED_EXTENSIONS].join(', ')} files are allowed.`);
216
- process.exit(5);
217
- }
218
- }
219
- }
220
-
221
- // Check for symlinks inside the archive
222
- const verboseResult = spawnSync('tar', ['tvzf', '-'], { input: decrypted, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
223
- if (verboseResult.status === 0) {
224
- for (const line of verboseResult.stdout.trim().split('\n')) {
225
- if (line.startsWith('l') || line.includes(' -> ')) {
226
- console.error(`BLOCKED: archive contains a symlink: "${line.trim()}"`);
227
- console.error('This file may be maliciously crafted. Aborting.');
228
- process.exit(5);
229
- }
230
- }
231
- }
232
-
233
- // --- Safe to extract (piped via stdin, no temp file on disk) ---
234
- fs.mkdirSync(outDir, { recursive: true });
235
- const extractResult = spawnSync('tar', ['xzf', '-', '-C', outDir], { input: decrypted, stdio: ['pipe', 'inherit', 'inherit'] });
236
- if (extractResult.status !== 0) {
237
- console.error('Failed to extract archive.');
238
- process.exit(4);
239
- }
240
-
241
- // Strip execute permissions from all extracted files
242
- const files = fs.readdirSync(outDir);
243
- for (const f of files) {
244
- const filePath = path.join(outDir, f);
245
- try {
246
- fs.chmodSync(filePath, 0o644);
247
- } catch {
248
- // Ignore permission errors on platforms that don't support chmod
249
- }
250
- }
251
-
252
- console.log(`Decrypted and extracted to: ${outDir}/`);
253
- console.log(`${files.length} file(s):`);
254
- files.forEach(f => console.log(` ${f}`));
255
- }
256
-
257
- main();