@homebridge-wyze-node24/homebridge-wyze-node24 1.1.5 → 1.1.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebridge-wyze-node24/homebridge-wyze-node24",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Wyze Smart Home plugin for Homebridge (Node.js 24 compatible)",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -1,3 +1,5 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
1
3
  const { homebridge, Accessory, UUIDGen } = require('./types')
2
4
  const { OutdoorPlugModels, PlugModels, CommonModels, CameraModels, LeakSensorModels,
3
5
  TemperatureHumidityModels, LockModels, MotionSensorModels, ContactSensorModels, LightModels,
@@ -29,6 +31,12 @@ const PLUGIN_NAME = 'homebridge-wyze-smart-home'
29
31
  const PLATFORM_NAME = 'WyzeSmartHome'
30
32
 
31
33
  const DEFAULT_REFRESH_INTERVAL = 30000
34
+ const AUTH_FILE_PATTERNS = [
35
+ /^wyze-.*\.json$/i,
36
+ /.*-wyze\.json$/i,
37
+ /^wyze-tokens\.json$/i
38
+ ]
39
+ const AUTH_RECOVERY_COOLDOWN_MS = 60000
32
40
 
33
41
  function delay(ms) {
34
42
  return new Promise(resolve => setTimeout(resolve, ms))
@@ -39,6 +47,7 @@ module.exports = class WyzeSmartHome {
39
47
  this.log = wrapLogger(log)
40
48
  this.config = resolveSecrets(config || {}, this.log)
41
49
  this.api = api
50
+ this.lastAuthRecoveryAt = 0
42
51
  this.client = this.getClient()
43
52
 
44
53
  this.accessories = []
@@ -50,6 +59,10 @@ module.exports = class WyzeSmartHome {
50
59
  homebridge.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, WyzeSmartHome)
51
60
  }
52
61
 
62
+ getPersistPath() {
63
+ return this.config.persistPath || homebridge.user.persistPath()
64
+ }
65
+
53
66
  getClient() {
54
67
  const { authBaseUrl, apiBaseUrl } = getValidatedBaseUrls(this.config, this.log)
55
68
 
@@ -65,7 +78,7 @@ module.exports = class WyzeSmartHome {
65
78
  //App Config
66
79
  lowBatteryPercentage: this.config.lowBatteryPercentage,
67
80
  //Storage Path
68
- persistPath: homebridge.user.persistPath(),
81
+ persistPath: this.getPersistPath(),
69
82
  //URLs (strictly validated)
70
83
  authBaseUrl,
71
84
  apiBaseUrl,
@@ -87,6 +100,59 @@ module.exports = class WyzeSmartHome {
87
100
  }, this.log)
88
101
  }
89
102
 
103
+ shouldRecoverFromAuthError(error) {
104
+ const message = String(error?.message || error || '')
105
+
106
+ return message.includes('access token is error') ||
107
+ message.includes('refresh token is error') ||
108
+ message.includes('Refresh Token could not be used to get a new access token')
109
+ }
110
+
111
+ clearPersistedAuth() {
112
+ const persistPath = this.getPersistPath()
113
+ let removedCount = 0
114
+
115
+ try {
116
+ const stat = fs.statSync(persistPath)
117
+
118
+ if (stat.isDirectory()) {
119
+ for (const fileName of fs.readdirSync(persistPath)) {
120
+ if (!AUTH_FILE_PATTERNS.some(pattern => pattern.test(fileName))) continue
121
+
122
+ fs.unlinkSync(path.join(persistPath, fileName))
123
+ removedCount += 1
124
+ }
125
+ } else if (stat.isFile()) {
126
+ fs.unlinkSync(persistPath)
127
+ removedCount = 1
128
+ }
129
+ } catch (error) {
130
+ if (error?.code !== 'ENOENT') {
131
+ this.log.warn(`Failed to clear persisted Wyze auth: ${error?.message || error}`)
132
+ }
133
+ }
134
+
135
+ return removedCount
136
+ }
137
+
138
+ recoverFromAuthError(error) {
139
+ if (!this.shouldRecoverFromAuthError(error)) {
140
+ return false
141
+ }
142
+
143
+ const now = Date.now()
144
+ if (now - this.lastAuthRecoveryAt < AUTH_RECOVERY_COOLDOWN_MS) {
145
+ return false
146
+ }
147
+
148
+ this.lastAuthRecoveryAt = now
149
+ const removedCount = this.clearPersistedAuth()
150
+ this.client = this.getClient()
151
+
152
+ this.log.warn(`Detected invalid Wyze auth tokens. Cleared ${removedCount} persisted auth file(s) from ${this.getPersistPath()} and recreated the Wyze client.`)
153
+ return true
154
+ }
155
+
90
156
  didFinishLaunching() {
91
157
  this.runLoop()
92
158
  }
@@ -124,6 +190,10 @@ module.exports = class WyzeSmartHome {
124
190
  if (this.config.pluginLoggingEnabled) this.log(`Found ${devices.length} device(s)`)
125
191
  await this.loadDevices(devices, timestamp)
126
192
  } catch (e) {
193
+ if (this.recoverFromAuthError(e)) {
194
+ return this.refreshDevices()
195
+ }
196
+
127
197
  this.log.error(`Error getting devices: ${e?.message || e}`)
128
198
  throw e
129
199
  }