@homebridge-plugins/homebridge-eufy-security 0.0.1

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 (185) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/FUNDING.yml +1 -0
  3. package/LICENSE +176 -0
  4. package/README.md +67 -0
  5. package/config.schema.json +6 -0
  6. package/dist/accessories/AutoSyncStationAccessory.js +156 -0
  7. package/dist/accessories/AutoSyncStationAccessory.js.map +1 -0
  8. package/dist/accessories/BaseAccessory.js +247 -0
  9. package/dist/accessories/BaseAccessory.js.map +1 -0
  10. package/dist/accessories/CameraAccessory.js +431 -0
  11. package/dist/accessories/CameraAccessory.js.map +1 -0
  12. package/dist/accessories/Device.js +67 -0
  13. package/dist/accessories/Device.js.map +1 -0
  14. package/dist/accessories/EntrySensorAccessory.js +48 -0
  15. package/dist/accessories/EntrySensorAccessory.js.map +1 -0
  16. package/dist/accessories/LockAccessory.js +142 -0
  17. package/dist/accessories/LockAccessory.js.map +1 -0
  18. package/dist/accessories/MotionSensorAccessory.js +48 -0
  19. package/dist/accessories/MotionSensorAccessory.js.map +1 -0
  20. package/dist/accessories/SmartDropAccessory.js +145 -0
  21. package/dist/accessories/SmartDropAccessory.js.map +1 -0
  22. package/dist/accessories/StationAccessory.js +371 -0
  23. package/dist/accessories/StationAccessory.js.map +1 -0
  24. package/dist/config.js +25 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/controller/LocalLivestreamManager.js +116 -0
  27. package/dist/controller/LocalLivestreamManager.js.map +1 -0
  28. package/dist/controller/recordingDelegate.js +208 -0
  29. package/dist/controller/recordingDelegate.js.map +1 -0
  30. package/dist/controller/snapshotDelegate.js +345 -0
  31. package/dist/controller/snapshotDelegate.js.map +1 -0
  32. package/dist/controller/streamingDelegate.js +345 -0
  33. package/dist/controller/streamingDelegate.js.map +1 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/interfaces.js +2 -0
  37. package/dist/interfaces.js.map +1 -0
  38. package/dist/media/Snapshot-Unavailable.png +0 -0
  39. package/dist/media/Snapshot-Unavailable.xcf +0 -0
  40. package/dist/media/Snapshot-black.png +0 -0
  41. package/dist/media/camera-disabled.png +0 -0
  42. package/dist/media/camera-offline.png +0 -0
  43. package/dist/media/media/Snapshot-Unavailable.png +0 -0
  44. package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
  45. package/dist/media/media/Snapshot-black.png +0 -0
  46. package/dist/media/media/camera-disabled.png +0 -0
  47. package/dist/media/media/camera-offline.png +0 -0
  48. package/dist/platform.js +716 -0
  49. package/dist/platform.js.map +1 -0
  50. package/dist/settings.js +38 -0
  51. package/dist/settings.js.map +1 -0
  52. package/dist/utils/Talkback.js +92 -0
  53. package/dist/utils/Talkback.js.map +1 -0
  54. package/dist/utils/accessoriesStore.js +206 -0
  55. package/dist/utils/accessoriesStore.js.map +1 -0
  56. package/dist/utils/configTypes.js +35 -0
  57. package/dist/utils/configTypes.js.map +1 -0
  58. package/dist/utils/ffmpeg.js +843 -0
  59. package/dist/utils/ffmpeg.js.map +1 -0
  60. package/dist/utils/interfaces.js +8 -0
  61. package/dist/utils/interfaces.js.map +1 -0
  62. package/dist/utils/utils.js +44 -0
  63. package/dist/utils/utils.js.map +1 -0
  64. package/dist/version.js +2 -0
  65. package/dist/version.js.map +1 -0
  66. package/eslint.config.mjs +18 -0
  67. package/homebridge-eufy-security.png +0 -0
  68. package/homebridge-ui/public/app.js +225 -0
  69. package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
  70. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
  71. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
  72. package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
  73. package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
  74. package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
  75. package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
  76. package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
  77. package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
  78. package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
  79. package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
  80. package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
  81. package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
  82. package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
  83. package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
  84. package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
  85. package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
  86. package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
  87. package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
  88. package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
  89. package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.jpg +0 -0
  90. package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
  91. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  92. package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
  93. package/homebridge-ui/public/assets/devices/homebasemini_large.jpg +0 -0
  94. package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
  95. package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
  96. package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
  97. package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
  98. package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
  99. package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
  100. package/homebridge-ui/public/assets/devices/indoorcams350_large.jpg +0 -0
  101. package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
  102. package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
  103. package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
  104. package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
  105. package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
  106. package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
  107. package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
  108. package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
  109. package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
  110. package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
  111. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
  112. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
  113. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
  114. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
  115. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
  116. package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
  117. package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
  118. package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
  119. package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
  120. package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
  121. package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
  122. package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
  123. package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
  124. package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
  125. package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
  126. package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
  127. package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
  128. package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
  129. package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
  130. package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
  131. package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
  132. package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
  133. package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
  134. package/homebridge-ui/public/assets/devices/unknown.png +0 -0
  135. package/homebridge-ui/public/assets/devices/walllight_s100_large.jpg +0 -0
  136. package/homebridge-ui/public/assets/devices/walllight_s120_large.jpg +0 -0
  137. package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
  138. package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
  139. package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
  140. package/homebridge-ui/public/assets/icons/attach.svg +1 -0
  141. package/homebridge-ui/public/assets/icons/battery_0.svg +1 -0
  142. package/homebridge-ui/public/assets/icons/battery_1.svg +1 -0
  143. package/homebridge-ui/public/assets/icons/battery_2.svg +1 -0
  144. package/homebridge-ui/public/assets/icons/battery_3.svg +1 -0
  145. package/homebridge-ui/public/assets/icons/battery_4.svg +1 -0
  146. package/homebridge-ui/public/assets/icons/battery_5.svg +1 -0
  147. package/homebridge-ui/public/assets/icons/battery_6.svg +1 -0
  148. package/homebridge-ui/public/assets/icons/bolt.svg +1 -0
  149. package/homebridge-ui/public/assets/icons/bug-report.svg +1 -0
  150. package/homebridge-ui/public/assets/icons/copy.svg +1 -0
  151. package/homebridge-ui/public/assets/icons/delete.svg +1 -0
  152. package/homebridge-ui/public/assets/icons/download.svg +1 -0
  153. package/homebridge-ui/public/assets/icons/info.svg +1 -0
  154. package/homebridge-ui/public/assets/icons/inventory.svg +1 -0
  155. package/homebridge-ui/public/assets/icons/refresh.svg +1 -0
  156. package/homebridge-ui/public/assets/icons/satellite_alt.svg +1 -0
  157. package/homebridge-ui/public/assets/icons/settings.svg +1 -0
  158. package/homebridge-ui/public/assets/icons/settings_backup_restore.svg +1 -0
  159. package/homebridge-ui/public/assets/icons/solar_power.svg +1 -0
  160. package/homebridge-ui/public/assets/icons/warning.svg +1 -0
  161. package/homebridge-ui/public/components/device-card.js +162 -0
  162. package/homebridge-ui/public/components/guard-modes.js +88 -0
  163. package/homebridge-ui/public/components/number-input.js +121 -0
  164. package/homebridge-ui/public/components/select.js +73 -0
  165. package/homebridge-ui/public/components/toggle.js +68 -0
  166. package/homebridge-ui/public/index.html +27 -0
  167. package/homebridge-ui/public/services/api.js +214 -0
  168. package/homebridge-ui/public/services/config.js +144 -0
  169. package/homebridge-ui/public/style.css +775 -0
  170. package/homebridge-ui/public/utils/countries.js +73 -0
  171. package/homebridge-ui/public/utils/device-images.js +89 -0
  172. package/homebridge-ui/public/utils/helpers.js +87 -0
  173. package/homebridge-ui/public/views/dashboard.js +226 -0
  174. package/homebridge-ui/public/views/device-detail.js +610 -0
  175. package/homebridge-ui/public/views/diagnostics.js +296 -0
  176. package/homebridge-ui/public/views/login.js +636 -0
  177. package/homebridge-ui/public/views/settings.js +192 -0
  178. package/homebridge-ui/public/views/unsupported-detail.js +296 -0
  179. package/homebridge-ui/server.js +1327 -0
  180. package/media/Snapshot-Unavailable.png +0 -0
  181. package/media/Snapshot-Unavailable.xcf +0 -0
  182. package/media/Snapshot-black.png +0 -0
  183. package/media/camera-disabled.png +0 -0
  184. package/media/camera-offline.png +0 -0
  185. package/package.json +64 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Country list for login form dropdown.
3
+ * Ported from src/configui/app/countries.ts
4
+ */
5
+ // eslint-disable-next-line no-unused-vars
6
+ const COUNTRIES = {
7
+ AF: 'Afghanistan', AX: 'Aland Islands', AL: 'Albania', DZ: 'Algeria',
8
+ AS: 'American Samoa', AD: 'Andorra', AO: 'Angola', AI: 'Anguilla',
9
+ AQ: 'Antarctica', AG: 'Antigua and Barbuda', AR: 'Argentina', AM: 'Armenia',
10
+ AW: 'Aruba', AU: 'Australia', AT: 'Austria', AZ: 'Azerbaijan',
11
+ BS: 'Bahamas', BH: 'Bahrain', BD: 'Bangladesh', BB: 'Barbados',
12
+ BY: 'Belarus', BE: 'Belgium', BZ: 'Belize', BJ: 'Benin', BM: 'Bermuda',
13
+ BT: 'Bhutan', BO: 'Bolivia', BA: 'Bosnia and Herzegovina', BW: 'Botswana',
14
+ BR: 'Brazil', BN: 'Brunei Darussalam', BG: 'Bulgaria', BF: 'Burkina Faso',
15
+ BI: 'Burundi', KH: 'Cambodia', CM: 'Cameroon', CA: 'Canada',
16
+ CV: 'Cape Verde', KY: 'Cayman Islands', CF: 'Central African Republic',
17
+ TD: 'Chad', CL: 'Chile', CN: 'China', CO: 'Colombia', KM: 'Comoros',
18
+ CG: 'Congo', CD: 'Congo, the Democratic Republic of the', CK: 'Cook Islands',
19
+ CR: 'Costa Rica', CI: "Cote D'Ivoire", HR: 'Croatia', CU: 'Cuba',
20
+ CW: 'Curacao', CY: 'Cyprus', CZ: 'Czech Republic', DK: 'Denmark',
21
+ DJ: 'Djibouti', DM: 'Dominica', DO: 'Dominican Republic', EC: 'Ecuador',
22
+ EG: 'Egypt', SV: 'El Salvador', GQ: 'Equatorial Guinea', ER: 'Eritrea',
23
+ EE: 'Estonia', ET: 'Ethiopia', FK: 'Falkland Islands (Malvinas)',
24
+ FO: 'Faroe Islands', FJ: 'Fiji', FI: 'Finland', FR: 'France',
25
+ GF: 'French Guiana', PF: 'French Polynesia', GA: 'Gabon', GM: 'Gambia',
26
+ GE: 'Georgia', DE: 'Germany', GH: 'Ghana', GI: 'Gibraltar', GR: 'Greece',
27
+ GL: 'Greenland', GD: 'Grenada', GP: 'Guadeloupe', GU: 'Guam',
28
+ GT: 'Guatemala', GG: 'Guernsey', GN: 'Guinea', GW: 'Guinea-Bissau',
29
+ GY: 'Guyana', HT: 'Haiti', VA: 'Holy See (Vatican City State)',
30
+ HN: 'Honduras', HK: 'Hong Kong', HU: 'Hungary', IS: 'Iceland', IN: 'India',
31
+ ID: 'Indonesia', IR: 'Iran, Islamic Republic of', IQ: 'Iraq', IE: 'Ireland',
32
+ IM: 'Isle of Man', IL: 'Israel', IT: 'Italy', JM: 'Jamaica', JP: 'Japan',
33
+ JE: 'Jersey', JO: 'Jordan', KZ: 'Kazakhstan', KE: 'Kenya', KI: 'Kiribati',
34
+ KP: "Korea, Democratic People's Republic of", KR: 'Korea, Republic of',
35
+ XK: 'Kosovo', KW: 'Kuwait', KG: 'Kyrgyzstan',
36
+ LA: "Lao People's Democratic Republic", LV: 'Latvia', LB: 'Lebanon',
37
+ LS: 'Lesotho', LR: 'Liberia', LY: 'Libyan Arab Jamahiriya',
38
+ LI: 'Liechtenstein', LT: 'Lithuania', LU: 'Luxembourg', MO: 'Macao',
39
+ MK: 'Macedonia, the Former Yugoslav Republic of', MG: 'Madagascar',
40
+ MW: 'Malawi', MY: 'Malaysia', MV: 'Maldives', ML: 'Mali', MT: 'Malta',
41
+ MH: 'Marshall Islands', MQ: 'Martinique', MR: 'Mauritania', MU: 'Mauritius',
42
+ YT: 'Mayotte', MX: 'Mexico', FM: 'Micronesia, Federated States of',
43
+ MD: 'Moldova, Republic of', MC: 'Monaco', MN: 'Mongolia', ME: 'Montenegro',
44
+ MS: 'Montserrat', MA: 'Morocco', MZ: 'Mozambique', MM: 'Myanmar',
45
+ NA: 'Namibia', NR: 'Nauru', NP: 'Nepal', NL: 'Netherlands',
46
+ NC: 'New Caledonia', NZ: 'New Zealand', NI: 'Nicaragua', NE: 'Niger',
47
+ NG: 'Nigeria', NU: 'Niue', NF: 'Norfolk Island',
48
+ MP: 'Northern Mariana Islands', NO: 'Norway', OM: 'Oman', PK: 'Pakistan',
49
+ PW: 'Palau', PS: 'Palestinian Territory, Occupied', PA: 'Panama',
50
+ PG: 'Papua New Guinea', PY: 'Paraguay', PE: 'Peru', PH: 'Philippines',
51
+ PN: 'Pitcairn', PL: 'Poland', PT: 'Portugal', PR: 'Puerto Rico',
52
+ QA: 'Qatar', RE: 'Reunion', RO: 'Romania', RU: 'Russian Federation',
53
+ RW: 'Rwanda', BL: 'Saint Barthelemy', SH: 'Saint Helena',
54
+ KN: 'Saint Kitts and Nevis', LC: 'Saint Lucia', MF: 'Saint Martin',
55
+ PM: 'Saint Pierre and Miquelon', VC: 'Saint Vincent and the Grenadines',
56
+ WS: 'Samoa', SM: 'San Marino', ST: 'Sao Tome and Principe',
57
+ SA: 'Saudi Arabia', SN: 'Senegal', RS: 'Serbia', SC: 'Seychelles',
58
+ SL: 'Sierra Leone', SG: 'Singapore', SX: 'Sint Maarten', SK: 'Slovakia',
59
+ SI: 'Slovenia', SB: 'Solomon Islands', SO: 'Somalia', ZA: 'South Africa',
60
+ SS: 'South Sudan', ES: 'Spain', LK: 'Sri Lanka', SD: 'Sudan',
61
+ SR: 'Suriname', SZ: 'Swaziland', SE: 'Sweden', CH: 'Switzerland',
62
+ SY: 'Syrian Arab Republic', TW: 'Taiwan, Province of China',
63
+ TJ: 'Tajikistan', TZ: 'Tanzania, United Republic of', TH: 'Thailand',
64
+ TL: 'Timor-Leste', TG: 'Togo', TK: 'Tokelau', TO: 'Tonga',
65
+ TT: 'Trinidad and Tobago', TN: 'Tunisia', TR: 'Turkey',
66
+ TM: 'Turkmenistan', TC: 'Turks and Caicos Islands', TV: 'Tuvalu',
67
+ UG: 'Uganda', UA: 'Ukraine', AE: 'United Arab Emirates',
68
+ GB: 'United Kingdom', US: 'United States', UY: 'Uruguay', UZ: 'Uzbekistan',
69
+ VU: 'Vanuatu', VE: 'Venezuela', VN: 'Viet Nam',
70
+ VG: 'Virgin Islands, British', VI: 'Virgin Islands, U.S.',
71
+ WF: 'Wallis and Futuna', EH: 'Western Sahara', YE: 'Yemen',
72
+ ZM: 'Zambia', ZW: 'Zimbabwe',
73
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Device type → image filename mapping.
3
+ * Ported from src/configui/app/util/deviceToImagesMap.ts
4
+ */
5
+ // eslint-disable-next-line no-unused-vars
6
+ const DeviceImages = {
7
+ getImage(deviceType) {
8
+ switch (deviceType) {
9
+ case 0: return 'homebase2_large.png';
10
+ case 1: case 4: return 'eufycam_large.jpg';
11
+ case 2: return 'sensor_large.png';
12
+ case 3: return 'floodlight_large.jpg';
13
+ case 5: return 'wireddoorbell2k_large.png';
14
+ case 7: case 16: return 'batterydoorbell2k_large.png';
15
+ case 8: return 'eufycam2c_large.jpg';
16
+ case 9: return 'eufycam2_large.png';
17
+ case 10: return 'motionsensor_large.png';
18
+ case 11: return 'keypad_large.png';
19
+ case 14: return 'eufycam2pro_large.jpg';
20
+ case 15: return 'eufycam2cpro_large.jpg';
21
+ case 18: return 'homebase3_large.png';
22
+ case 19: return 'eufycam3_large.jpg';
23
+ case 23: return 'eufycam3c_large.jpg';
24
+ case 24: return 'eufycame330_large.jpg';
25
+ case 25: return 'minibase_chime_T8023_large.jpg';
26
+ case 26: return 'eufycam3pro_large.png';
27
+ case 28: return 'homebasemini_large.jpg';
28
+ case 30: return 'indoorcamc120_large.png';
29
+ case 31: case 35: return 'indoorcamp24_large.png';
30
+ case 32: case 33: return 'solocame20_large.jpg';
31
+ case 34: return 'soloindoorcamc24_large.jpg';
32
+ case 37: return 'floodlight_large.jpg';
33
+ case 38: return 'floodlight2pro_large.jpg';
34
+ case 39: return 'floodlight_large.jpg';
35
+ case 44: case 46: return 'solooutdoorcamc22_large.png';
36
+ case 45: return 'solooutdoorcamc24_large.jpg';
37
+ case 47: return 'floodlightcame340_large.jpg';
38
+ case 48: return 'solocams340_large.png';
39
+ case 49: return 'solocame40_large.jpg';
40
+ case 50: return 'smartlock_touch_t8510_large.png';
41
+ case 51: return 'smartlock_touch_and_wifi_t8520_large.png';
42
+ case 52: case 53: return 'smartlock_t8500_large.png';
43
+ case 54: return 'smartlock_t8503_large.png';
44
+ case 55: return 'smartlock_video_t8530_large.png';
45
+ case 56: case 57: return 'smartlock_t8510P_t8520P_large.png';
46
+ case 58: return 'smartlock_t8504_large.jpg';
47
+ case 60: return 'solocaml20_large.jpg';
48
+ case 61: return 'solooutdoorcamc24_large.jpg';
49
+ case 62: return 'solocams40_large.jpg';
50
+ case 63: return 'solocams220_large.jpg';
51
+ case 64: return 'solocamc210_large.jpg';
52
+ case 87: return 'floodlight2_large.jpg';
53
+ case 88: return 'solocame30_large.png';
54
+ case 90: return 'smartdrop_t8790_large.png';
55
+ case 91: return 'batterydoorbell2kdual_large.jpg';
56
+ case 93: return 'wireddoorbelldual_large.jpg';
57
+ case 94: return 'batterydoorbell_e340_large.png';
58
+ case 95: return 'BATTERY_DOORBELL_C30.png';
59
+ case 96: return 'BATTERY_DOORBELL_C31.png';
60
+ case 100: return 'indoorcammini_large.jpg';
61
+ case 104: return 'indoorcams350_large.jpg';
62
+ case 105: return 'indoorcamE30_large.png';
63
+ case 101: case 102:
64
+ case 131: case 132: case 133:
65
+ return 'garage_camera_t8452_large.jpg';
66
+ case 110: return '4g_lte_starlight_large.jpg';
67
+ case 126: return 'sensor_large.png';
68
+ case 140: return 'smartsafe_s10_t7400_large.png';
69
+ case 141: return 'smartsafe_s12_t7401_large.png';
70
+ case 142: case 143: return 'smartsafe_s10_t7400_large.png';
71
+ case 151: return 'walllight_s100_large.jpg';
72
+ case 157: return 'smarttrack_link_t87B0_large.png';
73
+ case 159: return 'smarttrack_card_t87B2_large.png';
74
+ case 180: return 'smartlock_touch_and_wifi_t8502_large.png';
75
+ case 184: return 'smartlock_touch_and_wifi_t8506_large.png';
76
+ case 10005: return 'walllight_s120_large.jpg';
77
+ case 10008: return 'indoorcamC220_large.png';
78
+ case 10009: return 'indoorcamC210_large.png';
79
+ case 10010: return 'indoorcamC220_large.png';
80
+ case 10035: return 'solocamc35_large.png';
81
+ default: return 'unknown.png';
82
+ }
83
+ },
84
+
85
+ /** Returns full relative path to device image */
86
+ getPath(deviceType) {
87
+ return 'assets/devices/' + this.getImage(deviceType);
88
+ },
89
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Shared utility functions used across views and components.
3
+ */
4
+ // eslint-disable-next-line no-unused-vars
5
+ const Helpers = {
6
+ /**
7
+ * Escape HTML to prevent XSS when inserting user-supplied strings.
8
+ * @param {string} str
9
+ * @returns {string}
10
+ */
11
+ escHtml(str) {
12
+ const div = document.createElement('div');
13
+ div.textContent = str;
14
+ return div.innerHTML;
15
+ },
16
+
17
+ /**
18
+ * Append Node.js version warning text nodes to the given parent element.
19
+ * Uses DOM APIs exclusively — no innerHTML — to satisfy static-analysis security rules.
20
+ * @param {HTMLElement} parent
21
+ */
22
+ appendNodeVersionWarning(parent) {
23
+ const parts = [
24
+ ['text', 'Your Node.js version has removed '],
25
+ ['code', 'RSA_PKCS1_PADDING'],
26
+ ['text', ' support, which breaks streaming features. The rest of the plugin works fine. Use '],
27
+ ['strong', 'Node.js v20.11.0'],
28
+ ['text', ' or earlier, or upgrade to '],
29
+ ['strong', 'v24.5.0+'],
30
+ ['text', '. '],
31
+ ];
32
+ for (const [type, value] of parts) {
33
+ if (type === 'text') {
34
+ parent.appendChild(document.createTextNode(value));
35
+ } else {
36
+ const el = document.createElement(type);
37
+ el.textContent = value;
38
+ parent.appendChild(el);
39
+ }
40
+ }
41
+ const link = document.createElement('a');
42
+ link.href = 'https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Node.js-Compatibility-with-Eufy-Security-Plugin';
43
+ link.target = '_blank';
44
+ link.textContent = 'Learn more';
45
+ parent.appendChild(link);
46
+ },
47
+
48
+ /**
49
+ * Create an inline SVG icon <img> element from the assets/icons/ folder.
50
+ * @param {string} name Icon filename without path, e.g. 'warning.svg'
51
+ * @param {number} [size=16] Width/height in px
52
+ * @param {string} [alt=''] Alt text
53
+ * @returns {HTMLImageElement}
54
+ */
55
+ icon(name, size = 16, alt = '') {
56
+ const img = document.createElement('img');
57
+ img.src = 'assets/icons/' + name;
58
+ img.alt = alt;
59
+ img.width = size;
60
+ img.height = size;
61
+ img.style.verticalAlign = 'middle';
62
+ return img;
63
+ },
64
+
65
+ /**
66
+ * Return an HTML string for an inline SVG icon.
67
+ * Useful inside innerHTML / insertAdjacentHTML templates.
68
+ * @param {string} name Icon filename, e.g. 'warning.svg'
69
+ * @param {number} [size=16] Width/height in px
70
+ * @param {string} [alt=''] Alt text
71
+ * @returns {string}
72
+ */
73
+ iconHtml(name, size = 16, alt = '') {
74
+ return `<img src="assets/icons/${name}" alt="${alt}" width="${size}" height="${size}" style="vertical-align:middle">`;
75
+ },
76
+
77
+ /**
78
+ * Return the correct battery icon filename for a given percentage.
79
+ * Maps 0-100% to battery_0.svg … battery_6.svg (7 levels).
80
+ * @param {number} pct Battery percentage (0-100)
81
+ * @returns {string} e.g. 'battery_3.svg'
82
+ */
83
+ batteryIcon(pct) {
84
+ const level = Math.max(0, Math.min(6, Math.round((pct / 100) * 6)));
85
+ return 'battery_' + level + '.svg';
86
+ },
87
+ };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Dashboard View — displays device/station grid.
3
+ * Main screen after login. Shows all discovered devices as cards.
4
+ */
5
+ // eslint-disable-next-line no-unused-vars
6
+ const DashboardView = {
7
+
8
+ _container: null,
9
+
10
+ async render(container) {
11
+ this._container = container;
12
+ container.innerHTML = '';
13
+
14
+ const config = await Config.get();
15
+
16
+ // Header
17
+ const header = document.createElement('div');
18
+ header.className = 'eufy-header';
19
+
20
+ const titleEl = document.createElement('h4');
21
+ titleEl.textContent = 'Eufy Security';
22
+ header.appendChild(titleEl);
23
+
24
+ const headerRight = document.createElement('div');
25
+ headerRight.className = 'eufy-header__right';
26
+
27
+ // Cache date label
28
+ if (App.state.cacheDate) {
29
+ const cacheLabel = document.createElement('span');
30
+ cacheLabel.className = 'eufy-cache-date';
31
+ const d = new Date(App.state.cacheDate);
32
+ cacheLabel.textContent = 'Cached: ' + d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
33
+ cacheLabel.title = 'Last fetched: ' + d.toLocaleString();
34
+ headerRight.appendChild(cacheLabel);
35
+ }
36
+
37
+ const btnGroup = document.createElement('div');
38
+ btnGroup.className = 'd-flex gap-2';
39
+
40
+ const btnRefresh = this._headerBtn('refresh.svg', 'Re-login / Re-fetch', 'btn-success', () => App.navigate('login'));
41
+ const btnDiagnostic = this._headerBtn('bug-report.svg', 'Diagnostics', 'btn-warning', () => App.navigate('diagnostics'));
42
+ const btnSettings = this._headerBtn('settings.svg', 'Global Settings', 'btn-danger', () => App.navigate('settings'));
43
+
44
+ btnGroup.appendChild(btnRefresh);
45
+ btnGroup.appendChild(btnDiagnostic);
46
+ btnGroup.appendChild(btnSettings);
47
+ headerRight.appendChild(btnGroup);
48
+ header.appendChild(headerRight);
49
+ container.appendChild(header);
50
+
51
+ // Node.js version warning banner (shown if affected)
52
+ const warning = App.state.nodeVersionWarning;
53
+ if (warning && warning.affected) {
54
+ const banner = document.createElement('div');
55
+ banner.className = 'node-version-banner';
56
+
57
+ const iconDiv = document.createElement('div');
58
+ iconDiv.className = 'node-version-banner__icon';
59
+ iconDiv.appendChild(Helpers.icon('warning.svg', 24));
60
+ banner.appendChild(iconDiv);
61
+
62
+ const contentDiv = document.createElement('div');
63
+ contentDiv.className = 'node-version-banner__content';
64
+
65
+ const title = document.createElement('strong');
66
+ title.textContent = `Node.js ${warning.nodeVersion} — Streaming Incompatible`;
67
+ contentDiv.appendChild(title);
68
+
69
+ const textDiv = document.createElement('div');
70
+ textDiv.className = 'node-version-banner__text';
71
+ Helpers.appendNodeVersionWarning(textDiv);
72
+ contentDiv.appendChild(textDiv);
73
+
74
+ banner.appendChild(contentDiv);
75
+ container.appendChild(banner);
76
+ }
77
+
78
+ // Device grid
79
+ const stations = App.state.stations || [];
80
+
81
+ if (stations.length === 0) {
82
+ container.insertAdjacentHTML('beforeend', `
83
+ <div class="text-center text-muted py-5">
84
+ <div style="font-size: 2rem; margin-bottom: 12px;">${Helpers.iconHtml('inventory.svg', 32)}</div>
85
+ <p><strong>Your account is connected but no devices were found.</strong></p>
86
+ <p style="font-size: 0.85rem; max-width: 480px; margin: 0 auto;">
87
+ If you are using a guest admin account, make sure you have:<br>
88
+ 1. Logged into the <strong>official Eufy Security app</strong> with the guest account<br>
89
+ 2. <strong>Accepted the invitation</strong> from the main account<br>
90
+ 3. <strong>Logged out</strong> of the official app before refreshing here
91
+ </p>
92
+ <button class="btn btn-primary btn-sm mt-3" id="btn-go-login">Refresh Devices</button>
93
+ </div>
94
+ `);
95
+ container.querySelector('#btn-go-login').addEventListener('click', () => App.navigate('login'));
96
+ return;
97
+ }
98
+
99
+ const grid = document.createElement('div');
100
+ grid.className = 'row';
101
+
102
+ // Flatten: stations + their devices
103
+ const ignoreDevices = config.ignoreDevices || [];
104
+ const ignoreStations = config.ignoreStations || [];
105
+
106
+ // Separate supported and unsupported
107
+ const supported = [];
108
+ const unsupported = [];
109
+
110
+ stations.forEach((station) => {
111
+ // Check if station has a standalone device (device serial === station serial)
112
+ const standaloneDevice = (station.devices || []).find(
113
+ (d) => d.uniqueId === station.uniqueId
114
+ );
115
+
116
+ if (standaloneDevice) {
117
+ // Standalone: show as single card (the device IS the station)
118
+ const item = { ...standaloneDevice, type: standaloneDevice.type };
119
+ if (station.unsupported || item.unsupported) {
120
+ item.unsupported = true;
121
+ unsupported.push({ item, isStation: false, station });
122
+ } else {
123
+ supported.push({ item, isStation: false, station });
124
+ }
125
+ } else {
126
+ // Station card
127
+ if (!station.disabled) {
128
+ const stationItem = {
129
+ uniqueId: station.uniqueId,
130
+ displayName: station.displayName,
131
+ type: station.type,
132
+ typename: station.typename,
133
+ unsupported: station.unsupported,
134
+ ignored: station.ignored,
135
+ power: station.power,
136
+ };
137
+ if (station.unsupported) {
138
+ unsupported.push({ item: stationItem, isStation: true, station });
139
+ } else {
140
+ supported.push({ item: stationItem, isStation: true, station });
141
+ }
142
+ }
143
+
144
+ // Device cards for this station
145
+ (station.devices || []).forEach((device) => {
146
+ if (station.unsupported || device.unsupported) {
147
+ unsupported.push({ item: { ...device, unsupported: true }, isStation: false, station });
148
+ } else {
149
+ supported.push({ item: device, isStation: false, station });
150
+ }
151
+ });
152
+ }
153
+ });
154
+
155
+ // Render supported devices
156
+ supported.forEach(({ item, isStation }) => {
157
+ const isIgnored = isStation
158
+ ? ignoreStations.includes(item.uniqueId)
159
+ : ignoreDevices.includes(item.uniqueId);
160
+
161
+ DeviceCard.render(grid, {
162
+ device: { ...item, ignored: isIgnored },
163
+ isStation: isStation,
164
+ enabled: !isIgnored,
165
+ onClick: (d) => {
166
+ const type = isStation ? 'station' : 'device';
167
+ App.navigate('detail/' + type + '/' + d.uniqueId);
168
+ },
169
+ onToggle: (d, enabled) => {
170
+ const type = isStation ? 'station' : 'device';
171
+ Config.toggleIgnore(d.uniqueId, !enabled, type);
172
+ // Re-render to update visual state
173
+ this.render(this._container);
174
+ },
175
+ });
176
+ });
177
+
178
+ container.appendChild(grid);
179
+
180
+ // Unsupported section
181
+ if (unsupported.length > 0) {
182
+ const section = document.createElement('div');
183
+ section.className = 'mt-4';
184
+ section.innerHTML = `
185
+ <div class="detail-section__title">Unsupported Devices</div>
186
+ <p class="text-muted" style="font-size: 0.8rem;">
187
+ These devices were detected but are not yet fully supported by the eufy-security-client library.
188
+ Click on a device to find out how you can help us add support for it.
189
+ </p>
190
+ `;
191
+
192
+ const unsupportedGrid = document.createElement('div');
193
+ unsupportedGrid.className = 'row';
194
+
195
+ unsupported.forEach(({ item, isStation }) => {
196
+ DeviceCard.render(unsupportedGrid, {
197
+ device: { ...item, unsupported: true },
198
+ isStation: isStation,
199
+ enabled: false,
200
+ onClick: (d) => {
201
+ App.navigate('unsupported/' + d.uniqueId);
202
+ },
203
+ onToggle: () => {},
204
+ });
205
+ });
206
+
207
+ section.appendChild(unsupportedGrid);
208
+ container.appendChild(section);
209
+ }
210
+ },
211
+
212
+ _headerBtn(iconFile, label, btnClass, onClick) {
213
+ const btn = document.createElement('button');
214
+ btn.className = 'btn ' + btnClass + ' btn-sm eufy-tooltip';
215
+ btn.setAttribute('data-tooltip', label);
216
+ const img = document.createElement('img');
217
+ img.src = 'assets/icons/' + iconFile;
218
+ img.alt = label;
219
+ img.style.width = '24px';
220
+ img.style.height = '24px';
221
+ img.style.filter = 'brightness(0) invert(1)';
222
+ btn.appendChild(img);
223
+ btn.addEventListener('click', onClick);
224
+ return btn;
225
+ },
226
+ };