@homebridge-plugins/homebridge-ewelink 12.13.1-beta.2 → 12.14.1-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.
- package/CHANGELOG.md +13 -0
- package/lib/connection/ws.js +58 -107
- package/lib/device/simulation/garage-one.js +22 -10
- package/lib/platform.js +21 -13
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v12.14.1 (Pending Release)
|
|
6
|
+
|
|
7
|
+
- fix(garage-one): send single-outlet payload on switchSCM devices (#774) (@nico81)
|
|
8
|
+
- fix(ws): do not retry sendUpdate on timeout (#775) (@nico81)
|
|
9
|
+
- fix(platform): do not serialize hidden accessories to the cache
|
|
10
|
+
- chore: dependency updates
|
|
11
|
+
|
|
12
|
+
## v12.14.0 (2026-05-05)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- dependency updates
|
|
17
|
+
|
|
5
18
|
## v12.13.0 (2026-04-12)
|
|
6
19
|
|
|
7
20
|
### Changed
|
package/lib/connection/ws.js
CHANGED
|
@@ -11,7 +11,7 @@ import { hasProperty, parseError, sleep } from '../utils/functions.js'
|
|
|
11
11
|
const emitter = new events()
|
|
12
12
|
|
|
13
13
|
export default class {
|
|
14
|
-
constructor(platform, authData
|
|
14
|
+
constructor(platform, authData) {
|
|
15
15
|
// Set up variables from the platform
|
|
16
16
|
this.appId = platform.config.appId || platformConsts.appId
|
|
17
17
|
this.appSecret = platform.config.appSecret || platformConsts.appSecret
|
|
@@ -19,9 +19,6 @@ export default class {
|
|
|
19
19
|
this.lang = platform.lang
|
|
20
20
|
this.log = platform.log
|
|
21
21
|
|
|
22
|
-
// HTTP client used to refresh the bearer token when a 406 invalidates the session
|
|
23
|
-
this.httpClient = httpClient
|
|
24
|
-
|
|
25
22
|
// Set up variables from the authData (from HTTP)
|
|
26
23
|
this.httpHost = authData.httpHost
|
|
27
24
|
this.aToken = authData.aToken
|
|
@@ -29,51 +26,6 @@ export default class {
|
|
|
29
26
|
|
|
30
27
|
// Flag used to determine ws connection status
|
|
31
28
|
this.wsIsConnected = false
|
|
32
|
-
|
|
33
|
-
// Retry state — a single counter/timer serialises all reconnection paths
|
|
34
|
-
this.retryCount = 0
|
|
35
|
-
this.loginInProgress = false
|
|
36
|
-
this.reconnectTimer = null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
scheduleReconnect(reason) {
|
|
40
|
-
// Another retry is already queued or a login is actively running — nothing to do
|
|
41
|
-
if (this.reconnectTimer || this.loginInProgress) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
if (this.retryCount >= 10) {
|
|
45
|
-
this.log.warn('%s [giving up after %s attempts, reason: %s].', this.lang.wsLoginError, this.retryCount, reason)
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
const delay = Math.min(5000 * 2 ** this.retryCount, 300000)
|
|
49
|
-
this.retryCount += 1
|
|
50
|
-
// Log recovery at info level — users need to see that retries are happening
|
|
51
|
-
this.log('WS reconnect scheduled [%s] in %s ms (attempt %s).', reason, delay, this.retryCount)
|
|
52
|
-
this.reconnectTimer = setTimeout(() => {
|
|
53
|
-
this.reconnectTimer = null
|
|
54
|
-
this.login().catch(() => {
|
|
55
|
-
// login() schedules its own retry on internal failure — nothing to do here
|
|
56
|
-
})
|
|
57
|
-
}, delay)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async refreshHttpToken() {
|
|
61
|
-
// Re-run HTTP login to get a fresh aToken; a 406 means the old one is invalidated.
|
|
62
|
-
// This does NOT re-fetch homes/devices — only the bearer token changes.
|
|
63
|
-
if (!this.httpClient) {
|
|
64
|
-
return false
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
const authData = await this.httpClient.login()
|
|
68
|
-
this.aToken = authData.aToken
|
|
69
|
-
this.apiKey = authData.apiKey
|
|
70
|
-
this.httpHost = authData.httpHost
|
|
71
|
-
this.log('HTTP token refreshed after 406.')
|
|
72
|
-
return true
|
|
73
|
-
} catch (err) {
|
|
74
|
-
this.log.warn('%s %s.', 'HTTP token refresh failed:', parseError(err))
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
29
|
}
|
|
78
30
|
|
|
79
31
|
async getHost(attempt = 0) {
|
|
@@ -129,21 +81,17 @@ export default class {
|
|
|
129
81
|
}
|
|
130
82
|
|
|
131
83
|
async login() {
|
|
132
|
-
// Used to create the web socket connection and authenticate
|
|
133
|
-
// All reconnection paths (onClose, onError, 406, open-fail) funnel through
|
|
134
|
-
// scheduleReconnect(), which guards against concurrent logins via loginInProgress.
|
|
135
|
-
if (this.loginInProgress) {
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
this.loginInProgress = true
|
|
139
|
-
|
|
140
|
-
// Cancel any pending reconnect — we're attempting a login right now
|
|
141
|
-
if (this.reconnectTimer) {
|
|
142
|
-
clearTimeout(this.reconnectTimer)
|
|
143
|
-
this.reconnectTimer = null
|
|
144
|
-
}
|
|
145
|
-
|
|
84
|
+
// Used to create the web socket connection and authenticate
|
|
146
85
|
try {
|
|
86
|
+
// This may be called onClose and onError, we just want it to run once
|
|
87
|
+
if (this.debounce) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
this.debounce = true
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
this.debounce = false
|
|
93
|
+
}, 4000)
|
|
94
|
+
|
|
147
95
|
// Close any existing web socket connection
|
|
148
96
|
await this.closeConnection()
|
|
149
97
|
|
|
@@ -211,19 +159,20 @@ export default class {
|
|
|
211
159
|
}
|
|
212
160
|
}, (res.config.hbInterval + 7) * 1000)
|
|
213
161
|
|
|
214
|
-
// Login was successful
|
|
215
|
-
this.
|
|
162
|
+
// Login was successful and log if appropriate
|
|
163
|
+
this.authRetries = 0
|
|
216
164
|
if (this.debug) {
|
|
217
165
|
this.log('%s.', this.lang.wsLoginSuccess)
|
|
218
166
|
}
|
|
219
167
|
} else if (res.error === 406) {
|
|
220
|
-
// Token invalidated (e.g. concurrent session
|
|
221
|
-
// Re-sending userOnline with the same aToken will keep returning 406,
|
|
222
|
-
// so refresh the HTTP token first, then reconnect with backoff.
|
|
168
|
+
// Token invalidated (e.g. concurrent session), re-authenticate with backoff
|
|
223
169
|
this.log.warn(this.lang.wsLogin406)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.
|
|
170
|
+
const delay = Math.min(5000 * 2 ** (this.authRetries || 0), 300000)
|
|
171
|
+
this.authRetries = (this.authRetries || 0) + 1
|
|
172
|
+
if (this.authRetries <= 10) {
|
|
173
|
+
await sleep(delay)
|
|
174
|
+
await this.login()
|
|
175
|
+
}
|
|
227
176
|
} else {
|
|
228
177
|
// There was a problem with the authentication response
|
|
229
178
|
const str = JSON.stringify(res, null, 2)
|
|
@@ -232,8 +181,6 @@ export default class {
|
|
|
232
181
|
} catch (err) {
|
|
233
182
|
// Catch any errors authenticating the WS connection
|
|
234
183
|
this.log.warn('%s %s.', this.lang.wsLoginError, parseError(err))
|
|
235
|
-
await this.closeConnection()
|
|
236
|
-
this.scheduleReconnect('auth-error')
|
|
237
184
|
}
|
|
238
185
|
})
|
|
239
186
|
|
|
@@ -320,14 +267,14 @@ export default class {
|
|
|
320
267
|
msg.params.updateSource = 'WS'
|
|
321
268
|
emitter.emit('update', { deviceid: msg.deviceid, params: msg.params })
|
|
322
269
|
}
|
|
323
|
-
} else
|
|
270
|
+
} else {
|
|
324
271
|
// WS message received has an unknown action
|
|
325
272
|
this.log.warn('%s.\n%s', this.lang.wsUnkCmd, JSON.stringify(msg, null, 2))
|
|
326
273
|
}
|
|
327
274
|
})
|
|
328
275
|
|
|
329
276
|
// Add a listener for when the web socket closes for any reason
|
|
330
|
-
this.wsClient.onClose.addListener((e) => {
|
|
277
|
+
this.wsClient.onClose.addListener(async (e) => {
|
|
331
278
|
// Don't continue further with logging/reconnection if it's a wanted closure
|
|
332
279
|
if (e === 1005) {
|
|
333
280
|
return
|
|
@@ -337,23 +284,30 @@ export default class {
|
|
|
337
284
|
this.log.warn('%s [%s].', this.lang.wsReconnectError, e)
|
|
338
285
|
}
|
|
339
286
|
|
|
340
|
-
|
|
341
|
-
this.
|
|
287
|
+
// Reconnect with exponential backoff
|
|
288
|
+
this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
|
|
289
|
+
const delay = Math.min(5000 * 2 ** (this.reconnectAttempts - 1), 300000)
|
|
290
|
+
await sleep(delay)
|
|
291
|
+
await this.login()
|
|
342
292
|
})
|
|
343
293
|
|
|
344
294
|
// Add a listener for when the web socket throws an error
|
|
345
|
-
this.wsClient.onError.addListener((e) => {
|
|
295
|
+
this.wsClient.onError.addListener(async (e) => {
|
|
346
296
|
if (this.debug) {
|
|
347
297
|
this.log.warn('%s [%s].', this.lang.wsReconnectClose, e)
|
|
348
298
|
}
|
|
349
299
|
|
|
350
|
-
this.
|
|
300
|
+
this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
|
|
301
|
+
const delay = Math.min(5000 * 2 ** (this.reconnectAttempts - 1), 300000)
|
|
302
|
+
await sleep(delay)
|
|
303
|
+
await this.login()
|
|
351
304
|
})
|
|
352
305
|
|
|
353
|
-
// Open the web socket connection
|
|
354
|
-
// NB: retryCount is reset only on successful auth, not on TCP handshake —
|
|
355
|
-
// otherwise a repeated auth-failure loop would reset backoff every cycle.
|
|
306
|
+
// Open the web socket connection
|
|
356
307
|
await this.wsClient.open()
|
|
308
|
+
|
|
309
|
+
// Reset reconnect counter on successful connection
|
|
310
|
+
this.reconnectAttempts = 0
|
|
357
311
|
} catch (err) {
|
|
358
312
|
const errToShow = parseError(err)
|
|
359
313
|
|
|
@@ -361,13 +315,14 @@ export default class {
|
|
|
361
315
|
this.log('%s: %s.', this.lang.wsLoginErrRecon, errToShow)
|
|
362
316
|
}
|
|
363
317
|
|
|
364
|
-
this.
|
|
365
|
-
|
|
366
|
-
|
|
318
|
+
this.reconnectAttempts = (this.reconnectAttempts || 0) + 1
|
|
319
|
+
const delay = Math.min(5000 * 2 ** (this.reconnectAttempts - 1), 300000)
|
|
320
|
+
await sleep(delay)
|
|
321
|
+
await this.login()
|
|
367
322
|
}
|
|
368
323
|
}
|
|
369
324
|
|
|
370
|
-
async sendUpdate(json
|
|
325
|
+
async sendUpdate(json) {
|
|
371
326
|
// Generate the payload to send
|
|
372
327
|
const toSend = {
|
|
373
328
|
...json,
|
|
@@ -413,13 +368,11 @@ export default class {
|
|
|
413
368
|
throw new Error(`${this.lang.wsUnkRes} [${res.error}]`)
|
|
414
369
|
}
|
|
415
370
|
} catch (err) {
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
throw new Error(errMsg)
|
|
371
|
+
// Do not retry on timeout: the original command may have already been
|
|
372
|
+
// applied by the device but the cloud failed to deliver a formal ACK.
|
|
373
|
+
// Retrying non-idempotent writes (e.g. inching/momentary actuators)
|
|
374
|
+
// produces additional physical pulses, causing actuators to bounce.
|
|
375
|
+
throw new Error(parseError(err))
|
|
423
376
|
}
|
|
424
377
|
} else {
|
|
425
378
|
// The web socket isn't currently open so log this
|
|
@@ -464,8 +417,7 @@ export default class {
|
|
|
464
417
|
}
|
|
465
418
|
|
|
466
419
|
async closeConnection() {
|
|
467
|
-
// This is called when refreshing connection or if Homebridge shuts down
|
|
468
|
-
// Idempotent — safe to call regardless of current connection state.
|
|
420
|
+
// This is called when refreshing connection or if Homebridge shuts down
|
|
469
421
|
|
|
470
422
|
// Clear any existing ping interval
|
|
471
423
|
if (this.hbInterval) {
|
|
@@ -475,22 +427,21 @@ export default class {
|
|
|
475
427
|
|
|
476
428
|
// Check the ws client is set up
|
|
477
429
|
if (this.wsClient) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// Remove any existing listeners so onClose/onError don't retrigger reconnection
|
|
430
|
+
// Remove any existing listeners
|
|
481
431
|
this.wsClient.removeAllListeners()
|
|
482
|
-
this.wsIsConnected = false
|
|
483
432
|
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
433
|
+
// Close the web socket connection
|
|
434
|
+
if (this.wsIsConnected) {
|
|
435
|
+
// Set the connection flag to false
|
|
436
|
+
this.wsIsConnected = false
|
|
437
|
+
|
|
438
|
+
// Close the connection
|
|
487
439
|
await this.wsClient.close()
|
|
488
|
-
} catch {
|
|
489
|
-
// Already closed, never fully opened, or close threw — nothing actionable
|
|
490
|
-
}
|
|
491
440
|
|
|
492
|
-
|
|
493
|
-
|
|
441
|
+
// Log if appropriate
|
|
442
|
+
if (this.debug) {
|
|
443
|
+
this.log('%s.', this.lang.stoppedWS)
|
|
444
|
+
}
|
|
494
445
|
}
|
|
495
446
|
}
|
|
496
447
|
}
|
|
@@ -205,21 +205,33 @@ export default class {
|
|
|
205
205
|
await sleep(delay)
|
|
206
206
|
this.service.updateCharacteristic(this.hapChar.TargetDoorState, newPos)
|
|
207
207
|
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos + 2)
|
|
208
|
+
// SCM (single-channel multi-format) devices use the switches[] array
|
|
209
|
+
// but only expose outlet 0. True dual-channel garage controllers
|
|
210
|
+
// (e.g. DUALR3) use separate outlets for open/close relay action.
|
|
211
|
+
const isScm = platformConsts.devices.switchSCM.includes(this.accessory.context.eweUIID)
|
|
212
|
+
|| platformConsts.devices.switchSCMPower.includes(this.accessory.context.eweUIID)
|
|
213
|
+
|
|
208
214
|
switch (this.setup) {
|
|
209
215
|
case 'switchSingle':
|
|
210
216
|
params.switch = 'on'
|
|
211
217
|
break
|
|
212
218
|
case 'switchMulti':
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
if (isScm) {
|
|
220
|
+
// SCM device: send a single momentary pulse on outlet 0
|
|
221
|
+
params.switches = [{ switch: 'on', outlet: 0 }]
|
|
222
|
+
} else {
|
|
223
|
+
// Dual-channel device with separate open/close relays
|
|
224
|
+
params.switches = [
|
|
225
|
+
{
|
|
226
|
+
switch: newPos === 0 ? 'on' : 'off',
|
|
227
|
+
outlet: 0,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
switch: newPos === 1 ? 'on' : 'off',
|
|
231
|
+
outlet: 1,
|
|
232
|
+
},
|
|
233
|
+
]
|
|
234
|
+
}
|
|
223
235
|
break
|
|
224
236
|
default:
|
|
225
237
|
return
|
package/lib/platform.js
CHANGED
|
@@ -513,7 +513,7 @@ export default class {
|
|
|
513
513
|
|
|
514
514
|
// Set up the WS client, get the user WS host and login
|
|
515
515
|
if (this.config.mode !== 'lan') {
|
|
516
|
-
this.wsClient = new wsClient(this, authData
|
|
516
|
+
this.wsClient = new wsClient(this, authData)
|
|
517
517
|
await this.wsClient.login()
|
|
518
518
|
|
|
519
519
|
// Refresh the WS connection every 60 minutes
|
|
@@ -1427,8 +1427,7 @@ export default class {
|
|
|
1427
1427
|
}
|
|
1428
1428
|
|
|
1429
1429
|
// Update any changes to the sub accessory to the platform
|
|
1430
|
-
this.
|
|
1431
|
-
devicesInHB.set(subAccessory.UUID, subAccessory)
|
|
1430
|
+
this.saveAccessoryUpdate(subAccessory)
|
|
1432
1431
|
if (i === 0) {
|
|
1433
1432
|
accessory = subAccessory
|
|
1434
1433
|
}
|
|
@@ -1567,8 +1566,7 @@ export default class {
|
|
|
1567
1566
|
}
|
|
1568
1567
|
|
|
1569
1568
|
// Update any changes to the sub accessory to the platform
|
|
1570
|
-
this.
|
|
1571
|
-
devicesInHB.set(subAccessory.UUID, subAccessory)
|
|
1569
|
+
this.saveAccessoryUpdate(subAccessory)
|
|
1572
1570
|
|
|
1573
1571
|
// Increment the counter
|
|
1574
1572
|
rfChlCounter += Object.keys(subDevice.buttons || {}).length
|
|
@@ -1826,8 +1824,7 @@ export default class {
|
|
|
1826
1824
|
}
|
|
1827
1825
|
|
|
1828
1826
|
// Update any changes to the device into our devicesInHB map
|
|
1829
|
-
this.
|
|
1830
|
-
devicesInHB.set(accessory.UUID, accessory)
|
|
1827
|
+
this.saveAccessoryUpdate(accessory)
|
|
1831
1828
|
if (uiid === 5000) {
|
|
1832
1829
|
this.log(
|
|
1833
1830
|
'[%s] %s [%s] [%s].',
|
|
@@ -1947,6 +1944,20 @@ export default class {
|
|
|
1947
1944
|
}
|
|
1948
1945
|
}
|
|
1949
1946
|
|
|
1947
|
+
saveAccessoryUpdate(accessory) {
|
|
1948
|
+
// Persist any context/characteristic changes to Homebridge, then keep the
|
|
1949
|
+
// accessory in our in-memory map. Hidden accessories are deliberately never
|
|
1950
|
+
// registered (see addAccessory), so they have no associated plugin and would
|
|
1951
|
+
// make Homebridge throw when it serialises its accessory cache to disk
|
|
1952
|
+
// ('Cannot serialize accessory ... - missing associated plugin'). We still
|
|
1953
|
+
// keep them in devicesInHB so lookups (e.g. the SW0 master of a multi-channel
|
|
1954
|
+
// device) continue to work.
|
|
1955
|
+
if (!accessory.context.hidden) {
|
|
1956
|
+
this.api.updatePlatformAccessories([accessory])
|
|
1957
|
+
}
|
|
1958
|
+
devicesInHB.set(accessory.UUID, accessory)
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1950
1961
|
addExternalAccessory(device, hbDeviceId, category) {
|
|
1951
1962
|
try {
|
|
1952
1963
|
// Add the new accessory to Homebridge
|
|
@@ -2092,8 +2103,7 @@ export default class {
|
|
|
2092
2103
|
// The update is from WS so update the WS online/offline status
|
|
2093
2104
|
if (device.params.online !== accessory.context.reachableWAN) {
|
|
2094
2105
|
accessory.context.reachableWAN = device.params.online
|
|
2095
|
-
this.
|
|
2096
|
-
devicesInHB.set(accessory.UUID, accessory)
|
|
2106
|
+
this.saveAccessoryUpdate(accessory)
|
|
2097
2107
|
|
|
2098
2108
|
// Flag this true to update the sub accessories later
|
|
2099
2109
|
reachableChange = true
|
|
@@ -2154,8 +2164,7 @@ export default class {
|
|
|
2154
2164
|
|
|
2155
2165
|
// Update the accessory context if the device is back online or the IP changed
|
|
2156
2166
|
if (reachableChange) {
|
|
2157
|
-
this.
|
|
2158
|
-
devicesInHB.set(accessory.UUID, accessory)
|
|
2167
|
+
this.saveAccessoryUpdate(accessory)
|
|
2159
2168
|
}
|
|
2160
2169
|
}
|
|
2161
2170
|
|
|
@@ -2183,8 +2192,7 @@ export default class {
|
|
|
2183
2192
|
}
|
|
2184
2193
|
|
|
2185
2194
|
// Save the sub accessory updates to the platform
|
|
2186
|
-
this.
|
|
2187
|
-
devicesInHB.set(subAccessory.UUID, subAccessory)
|
|
2195
|
+
this.saveAccessoryUpdate(subAccessory)
|
|
2188
2196
|
}
|
|
2189
2197
|
}
|
|
2190
2198
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Homebridge eWeLink",
|
|
4
4
|
"alias": "eWeLink",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "12.
|
|
6
|
+
"version": "12.14.1-beta.0",
|
|
7
7
|
"description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "bwp91",
|
|
@@ -53,22 +53,22 @@
|
|
|
53
53
|
],
|
|
54
54
|
"main": "lib/index.js",
|
|
55
55
|
"engines": {
|
|
56
|
-
"homebridge": "^1.6.0 || ^2.0.0
|
|
57
|
-
"node": "^22 || ^24"
|
|
56
|
+
"homebridge": "^1.6.0 || ^2.0.0",
|
|
57
|
+
"node": "^22.12.0 || ^24.0.0"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"lint": "eslint . --max-warnings=0",
|
|
61
61
|
"lint:fix": "npm run lint -- --fix"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
65
|
-
"axios": "^1.
|
|
64
|
+
"@homebridge/plugin-ui-utils": "^2.2.4",
|
|
65
|
+
"axios": "^1.17.0",
|
|
66
66
|
"node-persist": "^4.0.4",
|
|
67
|
-
"p-queue": "^9.
|
|
67
|
+
"p-queue": "^9.3.0",
|
|
68
68
|
"websocket-as-promised": "^3.0.1",
|
|
69
|
-
"ws": "^8.
|
|
69
|
+
"ws": "^8.21.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@antfu/eslint-config": "^
|
|
72
|
+
"@antfu/eslint-config": "^9.0.0"
|
|
73
73
|
}
|
|
74
74
|
}
|