@homebridge-eufy-security/eufy-security-client 3.7.2-dev.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 (129) hide show
  1. package/.prettierignore/342/200/216 +8 -0
  2. package/.prettierrc +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +970 -0
  5. package/build/error.d.ts +138 -0
  6. package/build/error.js +190 -0
  7. package/build/error.js.map +1 -0
  8. package/build/eufysecurity.d.ts +180 -0
  9. package/build/eufysecurity.js +3148 -0
  10. package/build/eufysecurity.js.map +1 -0
  11. package/build/http/api.d.ts +119 -0
  12. package/build/http/api.js +1877 -0
  13. package/build/http/api.js.map +1 -0
  14. package/build/http/cache.d.ts +8 -0
  15. package/build/http/cache.js +34 -0
  16. package/build/http/cache.js.map +1 -0
  17. package/build/http/const.d.ts +8 -0
  18. package/build/http/const.js +3054 -0
  19. package/build/http/const.js.map +1 -0
  20. package/build/http/device.d.ts +490 -0
  21. package/build/http/device.js +5256 -0
  22. package/build/http/device.js.map +1 -0
  23. package/build/http/error.d.ts +73 -0
  24. package/build/http/error.js +101 -0
  25. package/build/http/error.js.map +1 -0
  26. package/build/http/index.d.ts +10 -0
  27. package/build/http/index.js +30 -0
  28. package/build/http/index.js.map +1 -0
  29. package/build/http/interfaces.d.ts +248 -0
  30. package/build/http/interfaces.js +3 -0
  31. package/build/http/interfaces.js.map +1 -0
  32. package/build/http/models.d.ts +608 -0
  33. package/build/http/models.js +3 -0
  34. package/build/http/models.js.map +1 -0
  35. package/build/http/parameter.d.ts +7 -0
  36. package/build/http/parameter.js +119 -0
  37. package/build/http/parameter.js.map +1 -0
  38. package/build/http/station.d.ts +382 -0
  39. package/build/http/station.js +15735 -0
  40. package/build/http/station.js.map +1 -0
  41. package/build/http/types.d.ts +1358 -0
  42. package/build/http/types.js +10333 -0
  43. package/build/http/types.js.map +1 -0
  44. package/build/http/utils.d.ts +89 -0
  45. package/build/http/utils.js +916 -0
  46. package/build/http/utils.js.map +1 -0
  47. package/build/index.d.ts +8 -0
  48. package/build/index.js +29 -0
  49. package/build/index.js.map +1 -0
  50. package/build/interfaces.d.ts +147 -0
  51. package/build/interfaces.js +7 -0
  52. package/build/interfaces.js.map +1 -0
  53. package/build/logging.d.ts +36 -0
  54. package/build/logging.js +119 -0
  55. package/build/logging.js.map +1 -0
  56. package/build/mqtt/interface.d.ts +6 -0
  57. package/build/mqtt/interface.js +3 -0
  58. package/build/mqtt/interface.js.map +1 -0
  59. package/build/mqtt/model.d.ts +24 -0
  60. package/build/mqtt/model.js +3 -0
  61. package/build/mqtt/model.js.map +1 -0
  62. package/build/mqtt/mqtt-eufy.crt +79 -0
  63. package/build/mqtt/proto/lock.proto +33 -0
  64. package/build/mqtt/service.d.ts +28 -0
  65. package/build/mqtt/service.js +196 -0
  66. package/build/mqtt/service.js.map +1 -0
  67. package/build/p2p/ble.d.ts +59 -0
  68. package/build/p2p/ble.js +281 -0
  69. package/build/p2p/ble.js.map +1 -0
  70. package/build/p2p/error.d.ts +49 -0
  71. package/build/p2p/error.js +69 -0
  72. package/build/p2p/error.js.map +1 -0
  73. package/build/p2p/index.d.ts +8 -0
  74. package/build/p2p/index.js +28 -0
  75. package/build/p2p/index.js.map +1 -0
  76. package/build/p2p/interfaces.d.ts +423 -0
  77. package/build/p2p/interfaces.js +3 -0
  78. package/build/p2p/interfaces.js.map +1 -0
  79. package/build/p2p/models.d.ts +295 -0
  80. package/build/p2p/models.js +3 -0
  81. package/build/p2p/models.js.map +1 -0
  82. package/build/p2p/session.d.ts +186 -0
  83. package/build/p2p/session.js +3737 -0
  84. package/build/p2p/session.js.map +1 -0
  85. package/build/p2p/talkback.d.ts +8 -0
  86. package/build/p2p/talkback.js +23 -0
  87. package/build/p2p/talkback.js.map +1 -0
  88. package/build/p2p/types.d.ts +1164 -0
  89. package/build/p2p/types.js +1219 -0
  90. package/build/p2p/types.js.map +1 -0
  91. package/build/p2p/utils.d.ts +72 -0
  92. package/build/p2p/utils.js +865 -0
  93. package/build/p2p/utils.js.map +1 -0
  94. package/build/push/client.d.ts +49 -0
  95. package/build/push/client.js +344 -0
  96. package/build/push/client.js.map +1 -0
  97. package/build/push/error.d.ts +73 -0
  98. package/build/push/error.js +101 -0
  99. package/build/push/error.js.map +1 -0
  100. package/build/push/index.d.ts +6 -0
  101. package/build/push/index.js +26 -0
  102. package/build/push/index.js.map +1 -0
  103. package/build/push/interfaces.d.ts +19 -0
  104. package/build/push/interfaces.js +3 -0
  105. package/build/push/interfaces.js.map +1 -0
  106. package/build/push/models.d.ts +328 -0
  107. package/build/push/models.js +38 -0
  108. package/build/push/models.js.map +1 -0
  109. package/build/push/parser.d.ts +25 -0
  110. package/build/push/parser.js +231 -0
  111. package/build/push/parser.js.map +1 -0
  112. package/build/push/proto/checkin.proto +266 -0
  113. package/build/push/proto/mcs.proto +328 -0
  114. package/build/push/service.d.ts +46 -0
  115. package/build/push/service.js +965 -0
  116. package/build/push/service.js.map +1 -0
  117. package/build/push/types.d.ts +220 -0
  118. package/build/push/types.js +244 -0
  119. package/build/push/types.js.map +1 -0
  120. package/build/push/utils.d.ts +7 -0
  121. package/build/push/utils.js +116 -0
  122. package/build/push/utils.js.map +1 -0
  123. package/build/utils.d.ts +115 -0
  124. package/build/utils.js +438 -0
  125. package/build/utils.js.map +1 -0
  126. package/eslint.config.mts +68 -0
  127. package/jest.config.js +14 -0
  128. package/package.json +85 -0
  129. package/scripts/cut_release.sh +31 -0
@@ -0,0 +1,916 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getWaitSeconds = exports.isSmartLockNotification = exports.switchSmartLockNotification = exports.getLockEventType = exports.getFloodLightT8425Notification = exports.isFloodlightT8425NotificationEnabled = exports.getIndoorNotification = exports.isIndoorNotificationEnabled = exports.getIndoorS350DetectionMode = exports.isIndoorS350DetectionModeEnabled = exports.getT8110DetectionMode = exports.isT8110DetectionModeEnabled = exports.getT8170DetectionMode = exports.isT8170DetectionModeEnabled = exports.decryptTrackerData = exports.isPrioritySourceType = exports.getImage = exports.getImagePath = exports.decodeImage = exports.getImageKey = exports.getImageSeed = exports.getImageBaseCode = exports.getIdSuffix = exports.randomNumber = exports.hexStringScheduleToSchedule = exports.hexWeek = exports.hexTime = exports.hexDate = exports.encodePasscode = exports.ParsePayload = exports.WritePayload = exports.getAdvancedLockTimezone = exports.getEufyTimezone = exports.getHB3DetectionMode = exports.isHB3DetectionModeEnabled = exports.isDeliveryPackageType = exports.getDistances = exports.getBlocklist = exports.decryptAPIData = exports.encryptAPIData = exports.calculateCellularSignalLevel = exports.calculateWifiSignalLevel = exports.switchNotificationMode = exports.isNotificationSwitchMode = exports.getImageFilePath = exports.getAbsoluteFilePath = exports.getTimezoneGMTString = exports.pad = exports.isGreaterEqualMinVersion = exports.normalizeVersionString = void 0;
7
+ exports.getRandomPhoneModel = exports.loadEventImage = exports.loadImageOverP2P = void 0;
8
+ const crypto_1 = require("crypto");
9
+ const const_1 = require("./const");
10
+ const md5_1 = __importDefault(require("crypto-js/md5"));
11
+ const enc_hex_1 = __importDefault(require("crypto-js/enc-hex"));
12
+ const sha256_1 = __importDefault(require("crypto-js/sha256"));
13
+ const types_1 = require("./types");
14
+ const error_1 = require("../error");
15
+ const error_2 = require("./error");
16
+ const types_2 = require("./../push/types");
17
+ const logging_1 = require("../logging");
18
+ const utils_1 = require("../utils");
19
+ const normalizeVersionString = function (version) {
20
+ /**
21
+ *
22
+ * Normalise version strings into an array of integers, otherwise if a version was not found it returns null
23
+ * Example of a version is 1.4.30.33
24
+ *
25
+ * @param version
26
+ */
27
+ const match = version.match(/\d+(?:\.\d+)+/);
28
+ if (match == null)
29
+ return null;
30
+ else {
31
+ return match[0].split(".").map(Number);
32
+ }
33
+ };
34
+ exports.normalizeVersionString = normalizeVersionString;
35
+ const isGreaterEqualMinVersion = function (minimal_version, current_version) {
36
+ /**
37
+ *
38
+ * Test the minimal version set is working with the current version by return true if it is equal or greater than min version.
39
+ *
40
+ * @param minimal_version
41
+ * @param current_version
42
+ */
43
+ const min_version = (0, exports.normalizeVersionString)(minimal_version);
44
+ const actual_version = (0, exports.normalizeVersionString)(current_version);
45
+ // Failed to parse actually version
46
+ if (actual_version === null)
47
+ return false;
48
+ // Failed to a parse min version but the current did, so we assume it is greater
49
+ if (min_version === null)
50
+ return true;
51
+ const version_slots = Math.min(min_version.length, actual_version.length);
52
+ let i;
53
+ // Loop for each slot to ensure it is greater or equal
54
+ for (i = 0; i < version_slots; i += 1) {
55
+ if (min_version[i] !== actual_version[i]) {
56
+ return min_version[i] < actual_version[i];
57
+ }
58
+ }
59
+ // If none of the slots are different but the length is the same, it is most likely the slots are the same
60
+ if (min_version.length === actual_version.length) {
61
+ return true;
62
+ }
63
+ return min_version.length < actual_version.length;
64
+ };
65
+ exports.isGreaterEqualMinVersion = isGreaterEqualMinVersion;
66
+ const pad = function (num) {
67
+ /**
68
+ *
69
+ * Convert the number to be absolute, round down and return "0" if lower than 10 or "" otherwise
70
+ * Seems to be used in a scenario where need to add a zero decimal to format a 2 digit string
71
+ *
72
+ * @param num
73
+ */
74
+ const norm = Math.floor(Math.abs(num));
75
+ return (norm < 10 ? "0" : "") + norm;
76
+ };
77
+ exports.pad = pad;
78
+ const getTimezoneGMTString = function () {
79
+ /**
80
+ * Get timezone to string
81
+ *
82
+ */
83
+ const tzo = -new Date().getTimezoneOffset();
84
+ const dif = tzo >= 0 ? "+" : "-";
85
+ return `GMT${dif}${(0, exports.pad)(tzo / 60)}:${(0, exports.pad)(tzo % 60)}`;
86
+ };
87
+ exports.getTimezoneGMTString = getTimezoneGMTString;
88
+ const getAbsoluteFilePath = function (device_type, channel, filename) {
89
+ /**
90
+ *
91
+ * Create the path based on two different devices
92
+ *
93
+ * @param device_type
94
+ * @param channel
95
+ * @param filename
96
+ */
97
+ // TODO : might need to extend to others device with local storage? not sure why only floodlight
98
+ const prefix = device_type === types_1.DeviceType.FLOODLIGHT ? const_1.PATH_DATA_CAMERA : const_1.PATH_MMC_CAMERA;
99
+ return `${prefix}${String(channel).padStart(2, "0")}/${filename}.dat`;
100
+ };
101
+ exports.getAbsoluteFilePath = getAbsoluteFilePath;
102
+ const getImageFilePath = function (device_type, channel, filename) {
103
+ /**
104
+ *
105
+ * Create the image path from a video filename
106
+ *
107
+ * @param device_type
108
+ * @param channel
109
+ * @param filename
110
+ */
111
+ // TODO : might need to extend to others device with local storage? not sure why only floodlight
112
+ const prefix = device_type === types_1.DeviceType.FLOODLIGHT ? const_1.PATH_DATA_VIDEO : const_1.PATH_MMC_VIDEO;
113
+ return `${prefix}/${filename}_c${String(channel).padStart(2, "0")}.jpg`;
114
+ };
115
+ exports.getImageFilePath = getImageFilePath;
116
+ const isNotificationSwitchMode = function (value, mode) {
117
+ /**
118
+ *
119
+ * Check if the mode is set to notification
120
+ *
121
+ * @param value
122
+ * @param mode
123
+ */
124
+ if (value === 1)
125
+ value = 240;
126
+ return (value & mode) !== 0;
127
+ };
128
+ exports.isNotificationSwitchMode = isNotificationSwitchMode;
129
+ const switchNotificationMode = function (currentValue, mode, enable) {
130
+ let result = 0;
131
+ if (!enable && currentValue === 1 /* ALL */) {
132
+ currentValue = 240;
133
+ }
134
+ if (enable) {
135
+ result = mode | currentValue;
136
+ }
137
+ else {
138
+ result = ~mode & currentValue;
139
+ }
140
+ if ((0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.SCHEDULE) &&
141
+ (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.APP) &&
142
+ (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.GEOFENCE) &&
143
+ (0, exports.isNotificationSwitchMode)(result, types_1.NotificationSwitchMode.KEYPAD)) {
144
+ result = 1; /* ALL */
145
+ }
146
+ return result;
147
+ };
148
+ exports.switchNotificationMode = switchNotificationMode;
149
+ // TODO : remove device , not needed
150
+ const calculateWifiSignalLevel = function (device, rssi) {
151
+ /**
152
+ * Calculate the signal strength based on the RSSI
153
+ *
154
+ * Using this scale for reference
155
+ * Excellent/Very Strong (-30 dBm to -50 dBm)
156
+ * Good/Strong (-50 dBm to -60 dBm)
157
+ * Fair/Good (-60 dBm to -67 dBm)
158
+ * Weak/Fair (-67 dBm to -70 dBm)
159
+ * Very Weak/Poor (-70 dBm to -80 dBm)
160
+ * Unusable (-80 dBm to -90 dBm or lower)
161
+ *
162
+ */
163
+ if (rssi >= -50)
164
+ return types_1.SignalLevel.FULL;
165
+ else if (rssi < -50 && rssi >= -60)
166
+ return types_1.SignalLevel.STRONG;
167
+ else if (rssi < -60 && rssi >= -67)
168
+ return types_1.SignalLevel.NORMAL;
169
+ else if (rssi < -67 && rssi >= -80)
170
+ return types_1.SignalLevel.WEAK;
171
+ else
172
+ return types_1.SignalLevel.NO_SIGNAL;
173
+ };
174
+ exports.calculateWifiSignalLevel = calculateWifiSignalLevel;
175
+ const calculateCellularSignalLevel = function (rssi) {
176
+ /**
177
+ * Calculate the signal strength from the RSSI ( this has a different scale than wifi )
178
+ *
179
+ * Excellent (>-65 to -70 dBm)
180
+ * Good (-70 to -85 dBm)
181
+ * Fair (-85 to -95 dBm)
182
+ * Poor (-95 to -100 dBm)
183
+ * Unusable/No Signal (<-100 to -110 dBm)
184
+ *
185
+ */
186
+ if (rssi >= 0)
187
+ return types_1.SignalLevel.NO_SIGNAL;
188
+ if (rssi >= -70)
189
+ return types_1.SignalLevel.FULL;
190
+ else if (rssi < -70 && rssi >= -85)
191
+ return types_1.SignalLevel.STRONG;
192
+ else if (rssi < -85 && rssi >= -95)
193
+ return types_1.SignalLevel.NORMAL;
194
+ else if (rssi < -95 && rssi >= -100)
195
+ return types_1.SignalLevel.WEAK;
196
+ else
197
+ return types_1.SignalLevel.NO_SIGNAL;
198
+ };
199
+ exports.calculateCellularSignalLevel = calculateCellularSignalLevel;
200
+ const encryptAPIData = (data, key) => {
201
+ const cipher = (0, crypto_1.createCipheriv)("aes-256-cbc", key, key.subarray(0, 16));
202
+ return cipher.update(data, "utf8", "base64") + cipher.final("base64");
203
+ };
204
+ exports.encryptAPIData = encryptAPIData;
205
+ const decryptAPIData = (data, key) => {
206
+ const cipher = (0, crypto_1.createDecipheriv)("aes-256-cbc", key, key.subarray(0, 16));
207
+ return Buffer.concat([cipher.update(data, "base64"), cipher.final()]);
208
+ };
209
+ exports.decryptAPIData = decryptAPIData;
210
+ const getBlocklist = function (distanceArray) {
211
+ /**
212
+ * It looks like it taken from the decompiled app from Eufy in the file SimpleDetectionGroup.java
213
+ *
214
+ * From asking, AI, this function it creates a bitmask representing which radar points meet that distance requirement.
215
+ * Finally, it applies a specific bitwise transformation to that mask.
216
+ *
217
+ * Potential description: Generates radar configuration parameters based on distance thresholds.
218
+ *
219
+ * * @param {number[]} distanceArray - An array of integers representing distances.
220
+ * * @returns {number[]} A list of 5 bitmask integers.
221
+ */
222
+ const requestParams = [];
223
+ for (let threshold = 1; threshold <= 5; threshold++) {
224
+ let bitmask = 0;
225
+ let bitPosition = 1;
226
+ // Iterate through each distance in the array
227
+ for (const distance of distanceArray) {
228
+ if (distance >= threshold) {
229
+ bitmask |= bitPosition;
230
+ }
231
+ // Shift bitPosition left by 1 (multiply by 2)
232
+ bitPosition <<= 1;
233
+ }
234
+ let finalValue = 0;
235
+ // Logic for specific bit patterns
236
+ if (bitmask === 0) {
237
+ finalValue = 0xffff; // 65535
238
+ }
239
+ else if (bitmask !== 0xff && bitmask !== 0xffff) {
240
+ // Flip the bits and add the 0xFF00 (65280) high-byte padding
241
+ finalValue = (bitmask ^ 0xff) + 0xff00;
242
+ }
243
+ requestParams.push(finalValue);
244
+ }
245
+ return requestParams;
246
+ };
247
+ exports.getBlocklist = getBlocklist;
248
+ const getDistances = function (rawDistanceData) {
249
+ /**
250
+ * It looks like it taken from the decompiled app from Eufy in the file SimpleDetectionGroup.java
251
+ *
252
+ * From asking AI:
253
+ * Processes raw radar sensor data to map object detections into spatial sectors.
254
+ * This method iterates through a list of distance data, where each element represents
255
+ * a depth level and its bits represent angular sectors. It inverts the bitmask,
256
+ * identifies active detections via bit-shifting, and updates a collection of
257
+ * RadarSelectInfo objects with the corresponding angle and proximity distance.
258
+ *
259
+ * @param rawDistanceData A list of integers where each bit represents a detection
260
+ * at a specific angle, and the list index represents depth.
261
+ */
262
+ const radarSectors = [3, 3, 3, 3, 3, 3, 3, 3];
263
+ let distanceStep = 0;
264
+ for (const rawValue of rawDistanceData) {
265
+ // Bitwise NOT/XOR to invert the signal (common in hardware where 0=detected)
266
+ let invertedBits = rawValue ^ 65535;
267
+ distanceStep++;
268
+ if (invertedBits !== 0) {
269
+ for (let i = 0; i < radarSectors.length; i++) {
270
+ const isObjectDetected = invertedBits & 1; // Check if the lowest bit is set
271
+ // If the bit was 1, mark this sector with the current distance
272
+ if (isObjectDetected > 0) {
273
+ radarSectors[i] = distanceStep;
274
+ }
275
+ // Shift bits to check the next angle in the next iteration
276
+ invertedBits = invertedBits >> 1;
277
+ }
278
+ }
279
+ }
280
+ return radarSectors;
281
+ };
282
+ exports.getDistances = getDistances;
283
+ const isDeliveryPackageType = function (value) {
284
+ /**
285
+ * Seems to be coming from EventData.java
286
+ *
287
+ */
288
+ return (value & 65536) == 65536;
289
+ };
290
+ exports.isDeliveryPackageType = isDeliveryPackageType;
291
+ const isHB3DetectionModeEnabled = function (value, type) {
292
+ /**
293
+ * Detection if Mode is enabled
294
+ *
295
+ */
296
+ const prefixCode = (type & value) == type;
297
+ if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) {
298
+ return prefixCode && (0, exports.isDeliveryPackageType)(value);
299
+ }
300
+ else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) {
301
+ return prefixCode && (value & 1) == 1;
302
+ }
303
+ return prefixCode;
304
+ };
305
+ exports.isHB3DetectionModeEnabled = isHB3DetectionModeEnabled;
306
+ const getHB3DetectionMode = function (value, type, enable) {
307
+ let result = 0;
308
+ if (!enable) {
309
+ if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) {
310
+ const tmp = (type & value) == type ? type ^ value : value;
311
+ result = (0, exports.isDeliveryPackageType)(value) ? tmp ^ 65536 : tmp;
312
+ }
313
+ else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) {
314
+ const tmp = (type & value) == type ? type ^ value : value;
315
+ result = (value & 1) == 1 ? tmp ^ 1 : tmp;
316
+ }
317
+ else {
318
+ result = type ^ value;
319
+ }
320
+ }
321
+ else {
322
+ if (type === types_1.HB3DetectionTypes.HUMAN_RECOGNITION) {
323
+ result = type | value | 65536;
324
+ }
325
+ else if (type === types_1.HB3DetectionTypes.HUMAN_DETECTION) {
326
+ result = type | value | 1;
327
+ }
328
+ else {
329
+ result = type | value;
330
+ }
331
+ }
332
+ return result;
333
+ };
334
+ exports.getHB3DetectionMode = getHB3DetectionMode;
335
+ const getEufyTimezone = function () {
336
+ for (const timezone of const_1.timeZoneData) {
337
+ if (timezone.timeId === Intl.DateTimeFormat().resolvedOptions().timeZone) {
338
+ return timezone;
339
+ }
340
+ }
341
+ return undefined;
342
+ };
343
+ exports.getEufyTimezone = getEufyTimezone;
344
+ const getAdvancedLockTimezone = function (stationSN) {
345
+ const timezone = (0, exports.getEufyTimezone)();
346
+ if (timezone !== undefined) {
347
+ // TODO: make this a method to check whatever we need to check for the station
348
+ if (stationSN.startsWith("T8520") && (0, exports.isGreaterEqualMinVersion)("1.2.8.6", stationSN))
349
+ return `${timezone.timeZoneGMT}|1.${timezone.timeSn}`;
350
+ else
351
+ return timezone.timeZoneGMT;
352
+ }
353
+ return "";
354
+ };
355
+ exports.getAdvancedLockTimezone = getAdvancedLockTimezone;
356
+ class WritePayload {
357
+ split_byte = -95;
358
+ data = Buffer.from([]);
359
+ write(bytes) {
360
+ const tmp_data = Buffer.from(bytes);
361
+ this.data = Buffer.concat([
362
+ this.data,
363
+ Buffer.from([this.split_byte]),
364
+ Buffer.from([tmp_data.length & 255]),
365
+ tmp_data,
366
+ ]);
367
+ this.split_byte += 1;
368
+ }
369
+ getData() {
370
+ return this.data;
371
+ }
372
+ }
373
+ exports.WritePayload = WritePayload;
374
+ class ParsePayload {
375
+ /**
376
+ * extract specific pieces of data from a binary buffer
377
+ *
378
+ * @private
379
+ */
380
+ data;
381
+ constructor(data) {
382
+ this.data = data;
383
+ }
384
+ readUint32BE(indexValue) {
385
+ return this.readData(indexValue).readUint32BE();
386
+ }
387
+ readUint32LE(indexValue) {
388
+ return this.readData(indexValue).readUint32LE();
389
+ }
390
+ readUint16BE(indexValue) {
391
+ return this.readData(indexValue).readUint16BE();
392
+ }
393
+ readUint16LE(indexValue) {
394
+ return this.readData(indexValue).readUint16LE();
395
+ }
396
+ readString(indexValue) {
397
+ return this.readData(indexValue).toString();
398
+ }
399
+ readStringHex(indexValue) {
400
+ return this.readData(indexValue).toString("hex");
401
+ }
402
+ readInt8(indexValue) {
403
+ let dataPosition = this.getDataPosition(indexValue);
404
+ if (dataPosition == -1) {
405
+ return 0;
406
+ }
407
+ dataPosition = dataPosition + 2;
408
+ if (dataPosition >= this.data.length) {
409
+ return 0;
410
+ }
411
+ return this.data.readInt8(dataPosition);
412
+ }
413
+ readData(indexValue) {
414
+ let dataPosition = this.getDataPosition(indexValue);
415
+ if (dataPosition == -1) {
416
+ return Buffer.from("");
417
+ }
418
+ dataPosition++;
419
+ if (dataPosition >= this.data.length) {
420
+ return Buffer.from("");
421
+ }
422
+ const nextStep = this.getNextStep(indexValue, dataPosition, this.data);
423
+ let tmp;
424
+ if (nextStep == 1) {
425
+ tmp = this.data.readInt8(dataPosition);
426
+ }
427
+ else {
428
+ tmp = this.data.readUint16LE(dataPosition);
429
+ }
430
+ if (dataPosition + nextStep + tmp > this.data.length) {
431
+ return Buffer.from("");
432
+ }
433
+ return this.data.subarray(dataPosition + nextStep, dataPosition + nextStep + tmp);
434
+ }
435
+ getDataPosition(indexValue) {
436
+ if (this.data && this.data.length >= 1) {
437
+ for (let currentPosition = 0; currentPosition < this.data.length;) {
438
+ if (this.data.readInt8(currentPosition) == indexValue) {
439
+ return currentPosition;
440
+ }
441
+ else {
442
+ const value = this.data.readInt8(currentPosition);
443
+ currentPosition++;
444
+ if (currentPosition >= this.data.length) {
445
+ break;
446
+ }
447
+ const nextStep = this.getNextStep(value, currentPosition, this.data);
448
+ if (currentPosition + nextStep >= this.data.length) {
449
+ break;
450
+ }
451
+ if (nextStep == 1) {
452
+ currentPosition = this.data.readInt8(currentPosition) + currentPosition + nextStep;
453
+ }
454
+ else {
455
+ currentPosition = this.data.readUint16LE(currentPosition) + currentPosition + nextStep;
456
+ }
457
+ }
458
+ }
459
+ }
460
+ return -1;
461
+ }
462
+ getNextStep(indexValue, position, data) {
463
+ const newPosition = position + 1 + data.readUInt8(position);
464
+ return newPosition == data.length || newPosition > data.length || data.readInt8(newPosition) == indexValue + 1
465
+ ? 1
466
+ : 2;
467
+ }
468
+ }
469
+ exports.ParsePayload = ParsePayload;
470
+ const encodePasscode = function (pass) {
471
+ /**
472
+ * Encode the passcode for smart lock
473
+ *
474
+ */
475
+ let result = "";
476
+ for (let i = 0; i < pass.length; i++)
477
+ result += pass.charCodeAt(i).toString(16);
478
+ return result;
479
+ };
480
+ exports.encodePasscode = encodePasscode;
481
+ const hexDate = function (date) {
482
+ const buf = Buffer.allocUnsafe(4);
483
+ buf.writeUint8(date.getDate());
484
+ buf.writeUint8(date.getMonth() + 1, 1);
485
+ buf.writeUint16BE(date.getFullYear(), 2);
486
+ return buf.readUInt32LE().toString(16).padStart(8, "0");
487
+ };
488
+ exports.hexDate = hexDate;
489
+ const hexTime = function (date) {
490
+ const buf = Buffer.allocUnsafe(2);
491
+ buf.writeUint8(date.getHours());
492
+ buf.writeUint8(date.getMinutes(), 1);
493
+ return buf.readUInt16BE().toString(16).padStart(4, "0");
494
+ };
495
+ exports.hexTime = hexTime;
496
+ const hexWeek = function (schedule) {
497
+ const SUNDAY = 1;
498
+ const MONDAY = 2;
499
+ const TUESDAY = 4;
500
+ const WEDNESDAY = 8;
501
+ const THURSDAY = 16;
502
+ const FRIDAY = 32;
503
+ const SATURDAY = 64;
504
+ let result = 0;
505
+ if (schedule.week !== undefined) {
506
+ if (schedule.week.sunday) {
507
+ result |= SUNDAY;
508
+ }
509
+ if (schedule.week.monday) {
510
+ result |= MONDAY;
511
+ }
512
+ if (schedule.week.tuesday) {
513
+ result |= TUESDAY;
514
+ }
515
+ if (schedule.week.wednesday) {
516
+ result |= WEDNESDAY;
517
+ }
518
+ if (schedule.week.thursday) {
519
+ result |= THURSDAY;
520
+ }
521
+ if (schedule.week.friday) {
522
+ result |= FRIDAY;
523
+ }
524
+ if (schedule.week.saturday) {
525
+ result |= SATURDAY;
526
+ }
527
+ return result.toString(16);
528
+ }
529
+ return "ff";
530
+ };
531
+ exports.hexWeek = hexWeek;
532
+ const hexStringScheduleToSchedule = function (startDay, startTime, endDay, endTime, week) {
533
+ const SUNDAY = 1;
534
+ const MONDAY = 2;
535
+ const TUESDAY = 4;
536
+ const WEDNESDAY = 8;
537
+ const THURSDAY = 16;
538
+ const FRIDAY = 32;
539
+ const SATURDAY = 64;
540
+ const weekNumber = Number.parseInt(week, 16);
541
+ return {
542
+ startDateTime: startDay === "00000000"
543
+ ? undefined
544
+ : new Date(Number.parseInt(`${startDay.substring(2, 4)}${startDay.substring(0, 2)}`, 16), Number.parseInt(startDay.substring(4, 6), 16) - 1, Number.parseInt(startDay.substring(6, 8), 16), Number.parseInt(startTime.substring(0, 2), 16), Number.parseInt(startTime.substring(2, 4), 16)),
545
+ endDateTime: endDay === "ffffffff"
546
+ ? undefined
547
+ : new Date(Number.parseInt(`${endDay.substring(2, 4)}${endDay.substring(0, 2)}`, 16), Number.parseInt(endDay.substring(4, 6), 16) - 1, Number.parseInt(endDay.substring(6, 8), 16), Number.parseInt(endTime.substring(0, 2), 16), Number.parseInt(endTime.substring(2, 4), 16)),
548
+ week: {
549
+ monday: (weekNumber & MONDAY) == MONDAY,
550
+ tuesday: (weekNumber & TUESDAY) == TUESDAY,
551
+ wednesday: (weekNumber & WEDNESDAY) == WEDNESDAY,
552
+ thursday: (weekNumber & THURSDAY) == THURSDAY,
553
+ friday: (weekNumber & FRIDAY) == FRIDAY,
554
+ saturday: (weekNumber & SATURDAY) == SATURDAY,
555
+ sunday: (weekNumber & SUNDAY) == SUNDAY,
556
+ },
557
+ };
558
+ };
559
+ exports.hexStringScheduleToSchedule = hexStringScheduleToSchedule;
560
+ const randomNumber = function (min, max) {
561
+ return Math.floor(Math.random() * (max - min + 1)) + min;
562
+ };
563
+ exports.randomNumber = randomNumber;
564
+ const getIdSuffix = function (p2pDid) {
565
+ let result = 0;
566
+ const match = p2pDid.match(/^[A-Z]+-(\d+)-[A-Z]+$/);
567
+ if (match?.length == 2) {
568
+ const num1 = Number.parseInt(match[1][0]);
569
+ const num2 = Number.parseInt(match[1][1]);
570
+ const num3 = Number.parseInt(match[1][3]);
571
+ const num4 = Number.parseInt(match[1][5]);
572
+ result = num1 + num2 + num3;
573
+ if (num3 < 5) {
574
+ result = result + num3;
575
+ }
576
+ result = result + num4;
577
+ }
578
+ return result;
579
+ };
580
+ exports.getIdSuffix = getIdSuffix;
581
+ const getImageBaseCode = function (serialNumber, p2pDid) {
582
+ let nr = 0;
583
+ try {
584
+ nr = Number.parseInt(`0x${serialNumber[serialNumber.length - 1]}`);
585
+ }
586
+ catch (err) {
587
+ const error = (0, error_1.ensureError)(err);
588
+ throw new error_2.ImageBaseCodeError("Error generating image base code", {
589
+ cause: error,
590
+ context: { serialnumber: serialNumber, p2pDid: p2pDid },
591
+ });
592
+ }
593
+ nr = (nr + 10) % 10;
594
+ const base = serialNumber.substring(nr);
595
+ return `${base}${(0, exports.getIdSuffix)(p2pDid)}`;
596
+ };
597
+ exports.getImageBaseCode = getImageBaseCode;
598
+ const getImageSeed = function (p2pDid, code) {
599
+ try {
600
+ const nCode = Number.parseInt(code.substring(2));
601
+ const prefix = 1000 - (0, exports.getIdSuffix)(p2pDid);
602
+ return (0, md5_1.default)(`${prefix}${nCode}`).toString(enc_hex_1.default).toUpperCase();
603
+ }
604
+ catch (err) {
605
+ const error = (0, error_1.ensureError)(err);
606
+ throw new error_2.ImageBaseCodeError("Error generating image seed", {
607
+ cause: error,
608
+ context: { p2pDid: p2pDid, code: code },
609
+ });
610
+ }
611
+ };
612
+ exports.getImageSeed = getImageSeed;
613
+ const getImageKey = function (serialNumber, p2pDid, code) {
614
+ const baseCode = (0, exports.getImageBaseCode)(serialNumber, p2pDid);
615
+ const seed = (0, exports.getImageSeed)(p2pDid, code);
616
+ const data = `01${baseCode}${seed}`;
617
+ const hash = (0, sha256_1.default)(data);
618
+ const hashBytes = [...Buffer.from(hash.toString(enc_hex_1.default), "hex")];
619
+ const startByte = hashBytes[10];
620
+ for (let i = 0; i < 32; i++) {
621
+ const byte = hashBytes[i];
622
+ let fixed_byte = startByte;
623
+ if (i < 31) {
624
+ fixed_byte = hashBytes[i + 1];
625
+ }
626
+ if (i == 31 || (i & 1) != 0) {
627
+ hashBytes[10] = fixed_byte;
628
+ if (126 < byte || 126 < hashBytes[10]) {
629
+ if (byte < hashBytes[10] || byte - hashBytes[10] == 0) {
630
+ hashBytes[i] = hashBytes[10] - byte;
631
+ }
632
+ else {
633
+ hashBytes[i] = byte - hashBytes[10];
634
+ }
635
+ }
636
+ }
637
+ else if (byte < 125 || fixed_byte < 125) {
638
+ hashBytes[i] = fixed_byte + byte;
639
+ }
640
+ }
641
+ return `${Buffer.from(hashBytes.slice(16)).toString("hex").toUpperCase()}`;
642
+ };
643
+ exports.getImageKey = getImageKey;
644
+ const decodeImage = function (p2pDid, data) {
645
+ if (data.length >= 12) {
646
+ const header = data.subarray(0, 12).toString();
647
+ if (header === "eufysecurity") {
648
+ const serialNumber = data.subarray(13, 29).toString();
649
+ const code = data.subarray(30, 40).toString();
650
+ const imageKey = (0, exports.getImageKey)(serialNumber, p2pDid, code);
651
+ const otherData = data.subarray(41);
652
+ const encryptedData = otherData.subarray(0, 256);
653
+ const cipher = (0, crypto_1.createDecipheriv)("aes-128-ecb", Buffer.from(imageKey, "utf-8").subarray(0, 16), null);
654
+ cipher.setAutoPadding(false);
655
+ const decryptedData = Buffer.concat([cipher.update(encryptedData), cipher.final()]);
656
+ decryptedData.copy(otherData);
657
+ return otherData;
658
+ }
659
+ }
660
+ return data;
661
+ };
662
+ exports.decodeImage = decodeImage;
663
+ const getImagePath = function (path) {
664
+ const splitPath = path.split("~");
665
+ if (splitPath.length === 2) {
666
+ return splitPath[1];
667
+ }
668
+ return path;
669
+ };
670
+ exports.getImagePath = getImagePath;
671
+ // TODO: make up some testing
672
+ const getImage = async function (api, serial, url) {
673
+ const { default: imageType } = await import("image-type");
674
+ const image = await api.getImage(serial, url);
675
+ const type = await imageType(image);
676
+ return {
677
+ data: image,
678
+ type: type !== null && type !== undefined ? type : { ext: "unknown", mime: "application/octet-stream" },
679
+ };
680
+ };
681
+ exports.getImage = getImage;
682
+ const isPrioritySourceType = function (current, update) {
683
+ return (((current === "http" || current === "p2p" || current === "push" || current === "mqtt" || current === undefined) &&
684
+ (update === "p2p" || update === "push" || update === "mqtt")) ||
685
+ ((current === "http" || current === undefined) && update === "http"));
686
+ };
687
+ exports.isPrioritySourceType = isPrioritySourceType;
688
+ const decryptTrackerData = (data, key) => {
689
+ const decipher = (0, crypto_1.createDecipheriv)("aes-128-ecb", key, null);
690
+ decipher.setAutoPadding(false);
691
+ return Buffer.concat([decipher.update(data), decipher.final()]);
692
+ };
693
+ exports.decryptTrackerData = decryptTrackerData;
694
+ // TODO: this seems to be used before above
695
+ const isT8170DetectionModeEnabled = function (value, type) {
696
+ return (type & value) == type;
697
+ };
698
+ exports.isT8170DetectionModeEnabled = isT8170DetectionModeEnabled;
699
+ // TODO this seems like getHB3DetectionMode
700
+ const getT8170DetectionMode = function (value, type, enable) {
701
+ let result = 0;
702
+ if (Object.values(types_1.T8170DetectionTypes).includes(type) &&
703
+ Object.values(types_1.T8170DetectionTypes).includes(value) &&
704
+ !enable)
705
+ return value;
706
+ if (!enable) {
707
+ result = type ^ value;
708
+ }
709
+ else {
710
+ result = type | value;
711
+ }
712
+ return result;
713
+ };
714
+ exports.getT8170DetectionMode = getT8170DetectionMode;
715
+ // TODO: Tidy up!!
716
+ const isT8110DetectionModeEnabled = function (value, type) {
717
+ return (type & value) == type;
718
+ };
719
+ exports.isT8110DetectionModeEnabled = isT8110DetectionModeEnabled;
720
+ const getT8110DetectionMode = function (value, type, enable) {
721
+ let result = 0;
722
+ if (Object.values(types_1.EufyCamC35DetectionTypes).includes(type) &&
723
+ Object.values(types_1.EufyCamC35DetectionTypes).includes(value) &&
724
+ !enable)
725
+ return value;
726
+ if (!enable) {
727
+ result = type ^ value;
728
+ }
729
+ else {
730
+ result = type | value;
731
+ }
732
+ return result;
733
+ };
734
+ exports.getT8110DetectionMode = getT8110DetectionMode;
735
+ // TODO: this is like isT8170DetectionModeEnabled
736
+ const isIndoorS350DetectionModeEnabled = function (value, type) {
737
+ return (type & value) == type;
738
+ };
739
+ exports.isIndoorS350DetectionModeEnabled = isIndoorS350DetectionModeEnabled;
740
+ // TODO: this is like getT8170DetectionMode
741
+ const getIndoorS350DetectionMode = function (value, type, enable) {
742
+ let result = 0;
743
+ if (Object.values(types_1.IndoorS350DetectionTypes).includes(type) &&
744
+ Object.values(types_1.IndoorS350DetectionTypes).includes(value) &&
745
+ !enable)
746
+ return value;
747
+ if (!enable) {
748
+ result = type ^ value;
749
+ }
750
+ else {
751
+ result = type | value;
752
+ }
753
+ return result;
754
+ };
755
+ exports.getIndoorS350DetectionMode = getIndoorS350DetectionMode;
756
+ // TODO: this is like isT8170DetectionModeEnabled
757
+ const isIndoorNotificationEnabled = function (value, type) {
758
+ return (type & value) == type;
759
+ };
760
+ exports.isIndoorNotificationEnabled = isIndoorNotificationEnabled;
761
+ // TODO: this is like getT8170DetectionMode
762
+ const getIndoorNotification = function (value, type, enable) {
763
+ let result = 0;
764
+ if (!enable) {
765
+ result = (type ^ value) + 800;
766
+ }
767
+ else {
768
+ result = type | value;
769
+ }
770
+ return result;
771
+ };
772
+ exports.getIndoorNotification = getIndoorNotification;
773
+ // TODO: this is like isT8170DetectionModeEnabled
774
+ const isFloodlightT8425NotificationEnabled = function (value, type) {
775
+ return (type & value) == type;
776
+ };
777
+ exports.isFloodlightT8425NotificationEnabled = isFloodlightT8425NotificationEnabled;
778
+ // TODO: this is like getT8170DetectionMode
779
+ const getFloodLightT8425Notification = function (value, type, enable) {
780
+ let result = 0;
781
+ if (!enable) {
782
+ result = type ^ value;
783
+ }
784
+ else {
785
+ result = type | value;
786
+ }
787
+ return result;
788
+ };
789
+ exports.getFloodLightT8425Notification = getFloodLightT8425Notification;
790
+ const getLockEventType = function (event) {
791
+ switch (event) {
792
+ case types_2.LockPushEvent.AUTO_LOCK:
793
+ case types_2.LockPushEvent.AUTO_UNLOCK:
794
+ return 1;
795
+ case types_2.LockPushEvent.MANUAL_LOCK:
796
+ case types_2.LockPushEvent.MANUAL_UNLOCK:
797
+ return 2;
798
+ case types_2.LockPushEvent.APP_LOCK:
799
+ case types_2.LockPushEvent.APP_UNLOCK:
800
+ return 3;
801
+ case types_2.LockPushEvent.PW_LOCK:
802
+ case types_2.LockPushEvent.PW_UNLOCK:
803
+ return 4;
804
+ case types_2.LockPushEvent.FINGER_LOCK:
805
+ case types_2.LockPushEvent.FINGERPRINT_UNLOCK:
806
+ return 5;
807
+ case types_2.LockPushEvent.TEMPORARY_PW_LOCK:
808
+ case types_2.LockPushEvent.TEMPORARY_PW_UNLOCK:
809
+ return 6;
810
+ case types_2.LockPushEvent.KEYPAD_LOCK:
811
+ return 7;
812
+ }
813
+ return 0;
814
+ };
815
+ exports.getLockEventType = getLockEventType;
816
+ // TODO: this is like getT8170DetectionMode
817
+ const switchSmartLockNotification = function (currentValue, mode, enable) {
818
+ let result = 0;
819
+ if (enable) {
820
+ result = mode | currentValue;
821
+ }
822
+ else {
823
+ result = ~mode & currentValue;
824
+ }
825
+ return result;
826
+ };
827
+ exports.switchSmartLockNotification = switchSmartLockNotification;
828
+ // TODO: this is like isT8170DetectionModeEnabled
829
+ const isSmartLockNotification = function (value, mode) {
830
+ return (value & mode) !== 0;
831
+ };
832
+ exports.isSmartLockNotification = isSmartLockNotification;
833
+ const getWaitSeconds = (device) => {
834
+ let seconds = 60;
835
+ const workingMode = device.getPropertyValue(types_1.PropertyName.DevicePowerWorkingMode);
836
+ if (workingMode !== undefined && workingMode === 2) {
837
+ const customValue = device.getPropertyValue(types_1.PropertyName.DeviceRecordingClipLength);
838
+ if (customValue !== undefined) {
839
+ seconds = customValue;
840
+ }
841
+ }
842
+ return seconds;
843
+ };
844
+ exports.getWaitSeconds = getWaitSeconds;
845
+ const loadImageOverP2P = function (station, device, id, p2pTimeouts) {
846
+ if (station.hasCommand(types_1.CommandName.StationDatabaseQueryLatestInfo) && p2pTimeouts.get(id) === undefined) {
847
+ const seconds = (0, exports.getWaitSeconds)(device);
848
+ p2pTimeouts.set(id, setTimeout(async () => {
849
+ station.databaseQueryLatestInfo();
850
+ p2pTimeouts.delete(id);
851
+ }, seconds * 1000));
852
+ }
853
+ };
854
+ exports.loadImageOverP2P = loadImageOverP2P;
855
+ const loadEventImage = function (station, api, device, message, p2pTimeouts) {
856
+ if (message.notification_style === types_1.NotificationType.MOST_EFFICIENT) {
857
+ (0, exports.loadImageOverP2P)(station, device, device.getSerial(), p2pTimeouts);
858
+ }
859
+ else {
860
+ if (!(0, utils_1.isEmpty)(message.pic_url)) {
861
+ (0, exports.getImage)(api, device.getSerial(), message.pic_url)
862
+ .then((image) => {
863
+ if (image.data.length > 0) {
864
+ if (p2pTimeouts.get(device.getSerial()) !== undefined) {
865
+ clearTimeout(p2pTimeouts.get(device.getSerial()));
866
+ p2pTimeouts.delete(device.getSerial());
867
+ }
868
+ device.updateProperty(types_1.PropertyName.DevicePicture, image, true);
869
+ }
870
+ else {
871
+ //fallback
872
+ (0, exports.loadImageOverP2P)(station, device, device.getSerial(), p2pTimeouts);
873
+ }
874
+ })
875
+ .catch((err) => {
876
+ const error = (0, error_1.ensureError)(err);
877
+ logging_1.rootHTTPLogger.debug(`Device load event image - Fallback Error`, {
878
+ error: (0, utils_1.getError)(error),
879
+ stationSN: station.getSerial(),
880
+ deviceSN: device.getSerial(),
881
+ message: JSON.stringify(message),
882
+ });
883
+ (0, exports.loadImageOverP2P)(station, device, device.getSerial(), p2pTimeouts);
884
+ });
885
+ }
886
+ else {
887
+ //fallback
888
+ (0, exports.loadImageOverP2P)(station, device, device.getSerial(), p2pTimeouts);
889
+ }
890
+ }
891
+ };
892
+ exports.loadEventImage = loadEventImage;
893
+ const getRandomPhoneModel = function () {
894
+ /**
895
+ * Generate a random phone model based on a structure
896
+ */
897
+ const brandKeys = Object.keys(const_1.PhoneModelStructure);
898
+ const randomBrandName = brandKeys[Math.floor(Math.random() * brandKeys.length)];
899
+ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
900
+ let part1 = "";
901
+ let part2 = "";
902
+ const dataBrand = const_1.PhoneModelStructure[randomBrandName];
903
+ if (dataBrand.first && dataBrand.second) {
904
+ part1 = pick(dataBrand.first);
905
+ part2 = pick(dataBrand.second);
906
+ }
907
+ if (dataBrand.numbers && dataBrand.letters) {
908
+ const minNum = Math.pow(10, dataBrand.numbers - 1);
909
+ const maxNum = Math.pow(10, dataBrand.numbers) - 1;
910
+ part1 = String(Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
911
+ part2 = pick(dataBrand.letters);
912
+ }
913
+ return `${randomBrandName}${part1}${part2}`.trim();
914
+ };
915
+ exports.getRandomPhoneModel = getRandomPhoneModel;
916
+ //# sourceMappingURL=utils.js.map