homebridge-ring 14.2.0 → 15.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # homebridge-ring
2
2
 
3
+ ## 15.0.0-beta.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#1846](https://github.com/dgreif/ring/pull/1846) [`2a6371d`](https://github.com/dgreif/ring/commit/2a6371d0890e35a80bf97fb1e1ccfa2a7c283b4e) Thanks [@dgreif](https://github.com/dgreif)! - Drop Node.js 20 support and upgrade undici to 8.x. Requires Node.js 22.19.0+ (22.x), 24.x, or 26.x; earlier 22.x releases are no longer supported.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#1892](https://github.com/dgreif/ring/pull/1892) [`8c9b637`](https://github.com/dgreif/ring/commit/8c9b63740f9d1bcd30a12922325eb58ac68e4cf9) Thanks [@dgreif](https://github.com/dgreif)! - Prevent failed volume, brightness, dimmer, and fan-speed writes from crashing Homebridge. Log failures and restore the last reported Ring level without overwriting newer requests.
12
+
13
+ - [#1886](https://github.com/dgreif/ring/pull/1886) [`78ae341`](https://github.com/dgreif/ring/commit/78ae341e990aa18517402a4e3809a51d3692678d) Thanks [@dgreif](https://github.com/dgreif)! - Never overwrite the stored refresh token when Ring's oauth response has no `refresh_token`. Previously, an undecodable response (for example Node.js 26 with an older undici) saved a token without `rt`, which caused permanent `invalid_grant` errors until a new token was generated
14
+
15
+ - [#1871](https://github.com/dgreif/ring/pull/1871) [`c72026d`](https://github.com/dgreif/ring/commit/c72026d72c7b9ccb9ef45b4f64c6affd86d85df2) Thanks [@dgreif](https://github.com/dgreif)! - Restore the reported location mode when a HomeKit mode change fails, without overwriting newer requests. Preserve pending Home mode targets until Ring confirms them.
16
+
17
+ - [#1746](https://github.com/dgreif/ring/pull/1746) [`25c6ec6`](https://github.com/dgreif/ring/commit/25c6ec63a9826f42f3f4269d0c2aad6f1e0e4185) Thanks [@kzaky](https://github.com/kzaky)! - Redact the 2FA code from the homebridge-ui server log on token exchange. Codes are short-lived and single-use, but logs are frequently pasted into bug reports, so removing the value from the log is a small defensive improvement.
18
+
19
+ - [#1838](https://github.com/dgreif/ring/pull/1838) [`c78c8b3`](https://github.com/dgreif/ring/commit/c78c8b3fadb0650d210d1220d2724309aec02611) Thanks [@dgreif](https://github.com/dgreif)! - Updated dependencies
20
+
21
+ - [#1840](https://github.com/dgreif/ring/pull/1840) [`5e4afd4`](https://github.com/dgreif/ring/commit/5e4afd43e88802e62bfc8acab743433d9bfaabfa) Thanks [@dgreif](https://github.com/dgreif)! - Bump werift to 0.26.0 (clears `ip` audit) and accept partial ICE candidates for Ring signaling
22
+ - Updated dependencies [[`e368e26`](https://github.com/dgreif/ring/commit/e368e26f789f4ca2093ac015dcec4bc9ce181d62), [`003c0d0`](https://github.com/dgreif/ring/commit/003c0d0740cedd3c393ce86baf628d7e5bc26c86), [`98f0cb3`](https://github.com/dgreif/ring/commit/98f0cb3f87473c168f4cb369ae26d486030e059e), [`bb781fe`](https://github.com/dgreif/ring/commit/bb781fe5ea8616c568a6f7d8e05a0d15fd472104), [`78ae341`](https://github.com/dgreif/ring/commit/78ae341e990aa18517402a4e3809a51d3692678d), [`2a6371d`](https://github.com/dgreif/ring/commit/2a6371d0890e35a80bf97fb1e1ccfa2a7c283b4e), [`c78c8b3`](https://github.com/dgreif/ring/commit/c78c8b3fadb0650d210d1220d2724309aec02611), [`5e4afd4`](https://github.com/dgreif/ring/commit/5e4afd43e88802e62bfc8acab743433d9bfaabfa)]:
23
+ - ring-client-api@15.0.0-beta.0
24
+
25
+ ## 14.3.0
26
+
27
+ ### Minor Changes
28
+
29
+ - [#1706](https://github.com/dgreif/ring/pull/1706) [`8381520`](https://github.com/dgreif/ring/commit/8381520f86d7d7813f670e3887b43d92542f0a41) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Add support for Ring Chime Pro nightlight
30
+
31
+ - [#1705](https://github.com/dgreif/ring/pull/1705) [`5360c16`](https://github.com/dgreif/ring/commit/5360c16f91d8680a20e68b2dd28e63cc0108c241) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Block streaming from cameras based on location mode settings. This may prevent you from streaming in cases where you could previously stream in HomeKit but not the Ring app.
32
+
33
+ - [#1707](https://github.com/dgreif/ring/pull/1707) [`5a8c844`](https://github.com/dgreif/ring/commit/5a8c844ffe4adc39e8a517628b26611fac36ef0a) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Add support for Kidde Ring Smoke + CO Alarm device (comp.bluejay.sensor_bluejay_wsc). Note, this has not been tested and may not be working yet.
34
+
35
+ ### Patch Changes
36
+
37
+ - [`875f024`](https://github.com/dgreif/ring/commit/875f024c5f27c55785d7250bc78eff0fda0a4aa9) Thanks [@dgreif](https://github.com/dgreif)! - Update dependencies
38
+
39
+ - Updated dependencies [[`8381520`](https://github.com/dgreif/ring/commit/8381520f86d7d7813f670e3887b43d92542f0a41), [`875f024`](https://github.com/dgreif/ring/commit/875f024c5f27c55785d7250bc78eff0fda0a4aa9), [`5360c16`](https://github.com/dgreif/ring/commit/5360c16f91d8680a20e68b2dd28e63cc0108c241), [`5a8c844`](https://github.com/dgreif/ring/commit/5a8c844ffe4adc39e8a517628b26611fac36ef0a)]:
40
+ - ring-client-api@14.3.0
41
+
3
42
  ## 14.2.0
4
43
 
5
44
  ### Minor Changes
@@ -1,7 +1,7 @@
1
- import { hap } from "./hap.js";
1
+ import { hap } from './hap.js';
2
2
  import { shareReplay, take } from 'rxjs/operators';
3
3
  import { firstValueFrom } from 'rxjs';
4
- import { debug } from "./config.js";
4
+ import { debug } from './config.js';
5
5
  import { logError, logInfo } from 'ring-client-api/util';
6
6
  function isServiceInstance(serviceType) {
7
7
  return typeof serviceType === 'object';
@@ -1,10 +1,10 @@
1
- import { BaseAccessory } from "./base-accessory.js";
2
- import { Subject } from 'rxjs';
1
+ import { BaseAccessory } from './base-accessory.js';
2
+ import { merge, Subject } from 'rxjs';
3
3
  import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
4
4
  import { logError } from 'ring-client-api/util';
5
5
  export class BaseDataAccessory extends BaseAccessory {
6
6
  registerCharacteristic({ characteristicType, serviceType, getValue, setValue, setValueDebounceTime = 0, name, requestUpdate, serviceSubType, }) {
7
- const service = this.getService(serviceType, name, serviceSubType), characteristic = service.getCharacteristic(characteristicType), { device } = this;
7
+ const service = this.getService(serviceType, name, serviceSubType), characteristic = service.getCharacteristic(characteristicType), { device } = this, onValue = new Subject();
8
8
  if (requestUpdate) {
9
9
  // Only register for GET if an async request should be made to get an updated value
10
10
  characteristic.on("get" /* CharacteristicEventTypes.GET */, (callback) => {
@@ -21,10 +21,17 @@ export class BaseDataAccessory extends BaseAccessory {
21
21
  if (setValue && setValueDebounceTime) {
22
22
  const onValueToSet = new Subject();
23
23
  characteristic.on("set" /* CharacteristicEventTypes.SET */, (newValue, callback) => {
24
+ onValue.next(newValue);
24
25
  onValueToSet.next(newValue);
25
26
  callback();
26
27
  });
27
- onValueToSet.pipe(debounceTime(setValueDebounceTime)).subscribe(setValue);
28
+ onValueToSet
29
+ .pipe(debounceTime(setValueDebounceTime))
30
+ .subscribe((value) => {
31
+ Promise.resolve()
32
+ .then(() => setValue(value))
33
+ .catch(logError);
34
+ });
28
35
  }
29
36
  else if (setValue) {
30
37
  characteristic.on("set" /* CharacteristicEventTypes.SET */, (newValue, callback) => {
@@ -32,14 +39,14 @@ export class BaseDataAccessory extends BaseAccessory {
32
39
  callback();
33
40
  });
34
41
  }
35
- ;
36
- this.device.onData
37
- .pipe(map(getValue), distinctUntilChanged())
42
+ merge(this.device.onData.pipe(map(getValue)), onValue)
43
+ .pipe(distinctUntilChanged())
38
44
  .subscribe((value) => characteristic.updateValue(value));
45
+ return (value) => onValue.next(value);
39
46
  }
40
47
  registerLevelCharacteristic({ characteristicType, serviceType, getValue, setValue, requestUpdate, }) {
41
- let targetLevel;
42
- this.registerCharacteristic({
48
+ let targetLevel, levelRequestId = 0;
49
+ const updateValue = this.registerCharacteristic({
43
50
  characteristicType,
44
51
  serviceType,
45
52
  getValue: (data) => {
@@ -49,9 +56,19 @@ export class BaseDataAccessory extends BaseAccessory {
49
56
  }
50
57
  return targetLevel === undefined ? newLevel : targetLevel;
51
58
  },
52
- setValue: (level) => {
59
+ setValue: async (level) => {
60
+ const requestId = ++levelRequestId;
53
61
  targetLevel = level;
54
- setValue(level);
62
+ try {
63
+ await setValue(level);
64
+ }
65
+ catch (e) {
66
+ if (requestId === levelRequestId) {
67
+ targetLevel = undefined;
68
+ updateValue(getValue(this.device.data));
69
+ }
70
+ throw e;
71
+ }
55
72
  },
56
73
  setValueDebounceTime: 500,
57
74
  requestUpdate,
@@ -1,5 +1,5 @@
1
- import { hap } from "./hap.js";
2
- import { BaseDataAccessory } from "./base-data-accessory.js";
1
+ import { hap } from './hap.js';
2
+ import { BaseDataAccessory } from './base-data-accessory.js';
3
3
  function getBatteryLevel({ batteryLevel, batteryStatus }) {
4
4
  if (batteryLevel !== undefined) {
5
5
  return batteryLevel;
package/lib/beam.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RingDeviceType } from 'ring-client-api';
2
- import { hap } from "./hap.js";
3
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
+ import { hap } from './hap.js';
3
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
4
4
  import { logInfo } from 'ring-client-api/util';
5
5
  export class Beam extends BaseDeviceAccessory {
6
6
  isLightGroup;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class BrightnessOnly extends BaseDeviceAccessory {
5
5
  device;
@@ -1,4 +1,4 @@
1
- import { hap } from "./hap.js";
1
+ import { hap } from './hap.js';
2
2
  import { generateSrtpOptions, ReturnAudioTranscoder, RtpSplitter, } from '@homebridge/camera-utils';
3
3
  import { logDebug, logError, logInfo } from 'ring-client-api/util';
4
4
  import { debounceTime, delay, take } from 'rxjs/operators';
package/lib/camera.js CHANGED
@@ -1,8 +1,8 @@
1
- import { hap } from "./hap.js";
2
- import { BaseDataAccessory } from "./base-data-accessory.js";
1
+ import { hap } from './hap.js';
2
+ import { BaseDataAccessory } from './base-data-accessory.js';
3
3
  import { filter, map, switchMap, throttleTime } from 'rxjs/operators';
4
- import { CameraSource } from "./camera-source.js";
5
- import { TargetValueTimer } from "./target-value-timer.js";
4
+ import { CameraSource } from './camera-source.js';
5
+ import { TargetValueTimer } from './target-value-timer.js';
6
6
  import { delay, logError, logInfo } from 'ring-client-api/util';
7
7
  import { firstValueFrom } from 'rxjs';
8
8
  export class Camera extends BaseDataAccessory {
package/lib/chime.js CHANGED
@@ -1,5 +1,5 @@
1
- import { hap } from "./hap.js";
2
- import { BaseDataAccessory } from "./base-data-accessory.js";
1
+ import { hap } from './hap.js';
2
+ import { BaseDataAccessory } from './base-data-accessory.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  const minutesFor24Hours = 24 * 60;
5
5
  export class Chime extends BaseDataAccessory {
@@ -11,7 +11,7 @@ export class Chime extends BaseDataAccessory {
11
11
  this.device = device;
12
12
  this.accessory = accessory;
13
13
  this.config = config;
14
- const { Characteristic, Service } = hap, snoozeService = this.getService(Service.Switch, device.name + ' Snooze', 'snooze'), playDingService = this.getService(Service.Switch, device.name + ' Play Ding', 'play-ding'), playMotionService = this.getService(Service.Switch, device.name + ' Play Motion', 'play-motion');
14
+ const { Characteristic, Service } = hap, snoozeService = this.getService(Service.Switch, device.name + ' Snooze', 'snooze'), playDingService = this.getService(Service.Switch, device.name + ' Play Ding', 'play-ding'), playMotionService = this.getService(Service.Switch, device.name + ' Play Motion', 'play-motion'), hasNightlight = device.data.settings.night_light_settings !== undefined;
15
15
  // Snooze Switch
16
16
  this.registerCharacteristic({
17
17
  characteristicType: Characteristic.On,
@@ -82,6 +82,20 @@ export class Chime extends BaseDataAccessory {
82
82
  },
83
83
  requestUpdate: () => device.requestUpdate(),
84
84
  });
85
+ // Nightlight (if supported)
86
+ if (hasNightlight) {
87
+ const nightlightService = this.getService(Service.Lightbulb, device.name + ' Nightlight', 'nightlight');
88
+ this.registerCharacteristic({
89
+ characteristicType: Characteristic.On,
90
+ serviceType: nightlightService,
91
+ getValue: (data) => Boolean(data.settings.night_light_settings?.light_sensor_enabled),
92
+ setValue: (enabled) => {
93
+ logInfo(`${device.name} nightlight ${enabled ? 'enabled' : 'disabled'}`);
94
+ return device.setNightlightEnabled(enabled);
95
+ },
96
+ requestUpdate: () => device.requestUpdate(),
97
+ });
98
+ }
85
99
  // Accessory Information Service
86
100
  this.registerCharacteristic({
87
101
  characteristicType: Characteristic.Manufacturer,
package/lib/co-alarm.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  export class CoAlarm extends BaseDeviceAccessory {
4
4
  device;
5
5
  accessory;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  export class ContactSensor extends BaseDeviceAccessory {
4
4
  device;
5
5
  accessory;
package/lib/fan.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class Fan extends BaseDeviceAccessory {
5
5
  device;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { distinctUntilChanged, filter, map } from 'rxjs/operators';
4
4
  import { logInfo } from 'ring-client-api/util';
5
5
  export class FloodFreezeSensor extends BaseDeviceAccessory {
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { distinctUntilChanged, filter, map } from 'rxjs/operators';
4
4
  import { logInfo } from 'ring-client-api/util';
5
5
  export class FreezeSensor extends BaseDeviceAccessory {
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-console */
2
2
  import { HomebridgePluginUiServer, RequestError, } from '@homebridge/plugin-ui-utils';
3
- import { RingRestClient } from 'ring-client-api/rest-client';
4
- import { controlCenterDisplayName, getSystemId } from "../config.js";
3
+ import { InvalidAuthResponseError, RingRestClient, } from 'ring-client-api/rest-client';
4
+ import { controlCenterDisplayName, getSystemId } from '../config.js';
5
5
  class PluginUiServer extends HomebridgePluginUiServer {
6
6
  restClient;
7
7
  constructor() {
@@ -36,12 +36,16 @@ class PluginUiServer extends HomebridgePluginUiServer {
36
36
  generateToken = async ({ email, password, code }) => {
37
37
  // use the existing restClient to avoid sending a token again
38
38
  this.restClient = this.restClient || new RingRestClient({ email, password });
39
- console.log(`Getting token for ${email} with code ${code}`);
39
+ console.log(`Getting token for ${email}`);
40
40
  try {
41
41
  const authResponse = await this.restClient.getAuth(code);
42
42
  return { refreshToken: authResponse.refresh_token };
43
43
  }
44
44
  catch (e) {
45
+ if (e instanceof InvalidAuthResponseError) {
46
+ console.error(e);
47
+ throw new RequestError(e.message, e);
48
+ }
45
49
  console.error('Incorrect 2fa Code');
46
50
  throw new RequestError('Please check the code and try again', e);
47
51
  }
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { platformName, pluginName, RingPlatform } from "./ring-platform.js";
2
- import { setHap } from "./hap.js";
1
+ import { platformName, pluginName, RingPlatform } from './ring-platform.js';
2
+ import { setHap } from './hap.js';
3
3
  export default function (homebridge) {
4
4
  setHap(homebridge.hap);
5
5
  homebridge.registerPlatform(pluginName, platformName, RingPlatform, true);
package/lib/intercom.js CHANGED
@@ -1,5 +1,5 @@
1
- import { hap } from "./hap.js";
2
- import { BaseDataAccessory } from "./base-data-accessory.js";
1
+ import { hap } from './hap.js';
2
+ import { BaseDataAccessory } from './base-data-accessory.js';
3
3
  import { logError, logInfo } from 'ring-client-api/util';
4
4
  import { map, throttleTime } from 'rxjs/operators';
5
5
  export class Intercom extends BaseDataAccessory {
@@ -1,7 +1,7 @@
1
1
  import { distinctUntilChanged } from 'rxjs/operators';
2
- import { hap } from "./hap.js";
2
+ import { hap } from './hap.js';
3
3
  import { logError, logInfo } from 'ring-client-api/util';
4
- import { BaseAccessory } from "./base-accessory.js";
4
+ import { BaseAccessory } from './base-accessory.js';
5
5
  import { firstValueFrom, of } from 'rxjs';
6
6
  function getStateFromMode(mode) {
7
7
  const { Characteristic: { SecuritySystemCurrentState: State }, } = hap;
@@ -18,6 +18,7 @@ function getStateFromMode(mode) {
18
18
  }
19
19
  export class LocationModeSwitch extends BaseAccessory {
20
20
  targetState;
21
+ modeRequestId = 0;
21
22
  device;
22
23
  location;
23
24
  accessory;
@@ -36,7 +37,7 @@ export class LocationModeSwitch extends BaseAccessory {
36
37
  if (state === this.targetState) {
37
38
  this.targetState = undefined;
38
39
  }
39
- if (!this.targetState) {
40
+ if (this.targetState === undefined) {
40
41
  targetState.updateValue(state);
41
42
  }
42
43
  currentState.updateValue(state);
@@ -58,28 +59,53 @@ export class LocationModeSwitch extends BaseAccessory {
58
59
  : await getCurrentState());
59
60
  });
60
61
  targetState.on("set" /* CharacteristicEventTypes.SET */, async (state, callback) => {
61
- const { Characteristic: { SecuritySystemTargetState: State }, } = hap;
62
+ const { Characteristic: { SecuritySystemTargetState: State }, } = hap, requestId = ++this.modeRequestId;
62
63
  callback();
63
- if (state === State.NIGHT_ARM) {
64
- state = State.STAY_ARM;
65
- // Ring doesn't have night mode, so switch over to stay mode
66
- setTimeout(() => targetState.updateValue(state), 100);
64
+ try {
65
+ const current = await getCurrentState();
66
+ if (requestId !== this.modeRequestId) {
67
+ return;
68
+ }
69
+ if (state === State.NIGHT_ARM) {
70
+ state = State.STAY_ARM;
71
+ // Ring doesn't have night mode, so switch over to stay mode
72
+ setTimeout(() => {
73
+ if (requestId === this.modeRequestId) {
74
+ targetState.updateValue(state);
75
+ }
76
+ }, 100);
77
+ }
78
+ if (state === current) {
79
+ this.targetState = undefined;
80
+ return;
81
+ }
82
+ this.targetState = state;
83
+ if (state === State.AWAY_ARM) {
84
+ logInfo(`Setting ${this.location.name} Mode to away`);
85
+ await this.location.setLocationMode('away');
86
+ }
87
+ else if (state === State.DISARM) {
88
+ logInfo(`Setting ${this.location.name} Mode to disarmed`);
89
+ await this.location.setLocationMode('disarmed');
90
+ }
91
+ else {
92
+ logInfo(`Setting ${this.location.name} Mode to home`);
93
+ await this.location.setLocationMode('home');
94
+ }
67
95
  }
68
- if (state === (await getCurrentState())) {
69
- this.targetState = undefined;
70
- return;
71
- }
72
- this.targetState = state;
73
- if (state === State.AWAY_ARM) {
74
- logInfo(`Setting ${this.location.name} Mode to away`);
75
- return this.location.setLocationMode('away');
76
- }
77
- else if (state === State.DISARM) {
78
- logInfo(`Setting ${this.location.name} Mode to disarmed`);
79
- return this.location.setLocationMode('disarmed');
96
+ catch (e) {
97
+ logError('Failed to set location mode for ' + location.name);
98
+ logError(e);
99
+ if (requestId !== this.modeRequestId) {
100
+ return;
101
+ }
102
+ const current = await getCurrentState();
103
+ if (requestId === this.modeRequestId) {
104
+ this.targetState = undefined;
105
+ this.modeRequestId++;
106
+ targetState.updateValue(current);
107
+ }
80
108
  }
81
- logInfo(`Setting ${this.location.name} Mode to home`);
82
- return this.location.setLocationMode('home');
83
109
  });
84
110
  targetState.setProps({
85
111
  validValues: [
package/lib/lock.js CHANGED
@@ -1,6 +1,6 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
2
  import { distinctUntilChanged } from 'rxjs/operators';
3
- import { hap } from "./hap.js";
3
+ import { hap } from './hap.js';
4
4
  function getCurrentState({ locked }) {
5
5
  const { Characteristic: { LockCurrentState: State }, } = hap;
6
6
  switch (locked) {
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  export class MotionSensor extends BaseDeviceAccessory {
4
4
  device;
5
5
  accessory;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class MultiLevelSwitch extends BaseDeviceAccessory {
5
5
  device;
package/lib/outlet.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class Outlet extends BaseDeviceAccessory {
5
5
  device;
@@ -1,5 +1,5 @@
1
- import { hap } from "./hap.js";
2
- import { BaseDataAccessory } from "./base-data-accessory.js";
1
+ import { hap } from './hap.js';
2
+ import { BaseDataAccessory } from './base-data-accessory.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  const burglarStates = [
5
5
  'burglar-alarm',
@@ -1,32 +1,32 @@
1
1
  import { RingApi, RingCamera, RingChime, RingDevice, RingDeviceCategory, RingDeviceType, RingIntercom, } from 'ring-client-api';
2
- import { hap } from "./hap.js";
3
- import { SecurityPanel } from "./security-panel.js";
4
- import { Chime } from "./chime.js";
5
- import { BrightnessOnly } from "./brightness-only.js";
6
- import { ContactSensor } from "./contact-sensor.js";
7
- import { MotionSensor } from "./motion-sensor.js";
8
- import { Lock } from "./lock.js";
9
- import { SmokeAlarm } from "./smoke-alarm.js";
10
- import { CoAlarm } from "./co-alarm.js";
11
- import { SmokeCoListener } from "./smoke-co-listener.js";
12
- import { controlCenterDisplayName, debug, getSystemId, updateHomebridgeConfig, } from "./config.js";
13
- import { Beam } from "./beam.js";
14
- import { MultiLevelSwitch } from "./multi-level-switch.js";
15
- import { Fan } from "./fan.js";
16
- import { Outlet } from "./outlet.js";
17
- import { Switch } from "./switch.js";
18
- import { Camera } from "./camera.js";
19
- import { PanicButtons } from "./panic-buttons.js";
2
+ import { hap } from './hap.js';
3
+ import { SecurityPanel } from './security-panel.js';
4
+ import { Chime } from './chime.js';
5
+ import { BrightnessOnly } from './brightness-only.js';
6
+ import { ContactSensor } from './contact-sensor.js';
7
+ import { MotionSensor } from './motion-sensor.js';
8
+ import { Lock } from './lock.js';
9
+ import { SmokeAlarm } from './smoke-alarm.js';
10
+ import { CoAlarm } from './co-alarm.js';
11
+ import { SmokeCoListener } from './smoke-co-listener.js';
12
+ import { controlCenterDisplayName, debug, getSystemId, updateHomebridgeConfig, } from './config.js';
13
+ import { Beam } from './beam.js';
14
+ import { MultiLevelSwitch } from './multi-level-switch.js';
15
+ import { Fan } from './fan.js';
16
+ import { Outlet } from './outlet.js';
17
+ import { Switch } from './switch.js';
18
+ import { Camera } from './camera.js';
19
+ import { PanicButtons } from './panic-buttons.js';
20
20
  import { logInfo, useLogger } from 'ring-client-api/util';
21
- import { FloodFreezeSensor } from "./flood-freeze-sensor.js";
22
- import { FreezeSensor } from "./freeze-sensor.js";
23
- import { TemperatureSensor } from "./temperature-sensor.js";
24
- import { WaterSensor } from "./water-sensor.js";
25
- import { LocationModeSwitch } from "./location-mode-switch.js";
26
- import { Thermostat } from "./thermostat.js";
27
- import { UnknownZWaveSwitchSwitch } from "./unknown-zwave-switch.js";
28
- import { Intercom } from "./intercom.js";
29
- import { Valve } from "./valve.js";
21
+ import { FloodFreezeSensor } from './flood-freeze-sensor.js';
22
+ import { FreezeSensor } from './freeze-sensor.js';
23
+ import { TemperatureSensor } from './temperature-sensor.js';
24
+ import { WaterSensor } from './water-sensor.js';
25
+ import { LocationModeSwitch } from './location-mode-switch.js';
26
+ import { Thermostat } from './thermostat.js';
27
+ import { UnknownZWaveSwitchSwitch } from './unknown-zwave-switch.js';
28
+ import { Intercom } from './intercom.js';
29
+ import { Valve } from './valve.js';
30
30
  const ignoreHiddenDeviceTypes = [
31
31
  RingDeviceType.RingNetAdapter,
32
32
  RingDeviceType.ZigbeeAdapter,
@@ -39,7 +39,7 @@ const ignoreHiddenDeviceTypes = [
39
39
  ];
40
40
  export const platformName = 'Ring';
41
41
  export const pluginName = 'homebridge-ring';
42
- function getAccessoryClass(device) {
42
+ export function getAccessoryClass(device) {
43
43
  const { deviceType } = device;
44
44
  if (device.data.status === 'disabled') {
45
45
  return null;
@@ -67,6 +67,7 @@ function getAccessoryClass(device) {
67
67
  case RingDeviceType.CoAlarm:
68
68
  return CoAlarm;
69
69
  case RingDeviceType.SmokeCoListener:
70
+ case RingDeviceType.KiddeSmokeCoAlarm:
70
71
  return SmokeCoListener;
71
72
  case RingDeviceType.BeamsMotionSensor:
72
73
  case RingDeviceType.BeamsSwitch:
@@ -228,8 +229,7 @@ export class RingPlatform {
228
229
  }
229
230
  const createHomebridgeAccessory = () => {
230
231
  const accessory = new api.platformAccessory(displayName, uuid, isCamera
231
- ? 17 /* hap.Categories.CAMERA */
232
- : 11 /* hap.Categories.SECURITY_SYSTEM */);
232
+ ? 17 /* hap.Categories.CAMERA */ : 11 /* hap.Categories.SECURITY_SYSTEM */);
233
233
  if (isCamera) {
234
234
  logInfo(`Configured camera ${uuid} ${deviceType} ${displayName}`);
235
235
  externalAccessories.push(accessory);
@@ -1,6 +1,6 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
2
  import { allAlarmStates, RingDeviceType } from 'ring-client-api';
3
- import { hap } from "./hap.js";
3
+ import { hap } from './hap.js';
4
4
  import { logError, logInfo } from 'ring-client-api/util';
5
5
  function isValidNightModeBypass(mode) {
6
6
  return mode && (mode === 'all' || mode === 'some');
@@ -45,7 +45,7 @@ export class SecurityPanel extends BaseDeviceAccessory {
45
45
  characteristicType: Characteristic.On,
46
46
  serviceType: Service.Switch,
47
47
  name: this.device.name + ' Siren',
48
- getValue: (data) => data.siren && data.siren.state === 'on',
48
+ getValue: (data) => Boolean(data.siren && data.siren.state === 'on'),
49
49
  setValue: (value) => {
50
50
  if (value) {
51
51
  return this.device.location.soundSiren();
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  export class SmokeAlarm extends BaseDeviceAccessory {
4
4
  device;
5
5
  accessory;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  export class SmokeCoListener extends BaseDeviceAccessory {
4
4
  device;
5
5
  accessory;
@@ -14,7 +14,9 @@ export class SmokeCoListener extends BaseDeviceAccessory {
14
14
  characteristicType: SmokeDetected,
15
15
  serviceType: SmokeSensor,
16
16
  getValue: (data) => {
17
- return data.smoke && data.smoke.alarmStatus === 'active'
17
+ const smokeAlarmStatus = data.smoke?.alarmStatus ??
18
+ data.components?.['alarm.smoke']?.alarmStatus;
19
+ return smokeAlarmStatus === 'active'
18
20
  ? SmokeDetected.SMOKE_DETECTED
19
21
  : SmokeDetected.SMOKE_NOT_DETECTED;
20
22
  },
@@ -23,7 +25,8 @@ export class SmokeCoListener extends BaseDeviceAccessory {
23
25
  characteristicType: CarbonMonoxideDetected,
24
26
  serviceType: CarbonMonoxideSensor,
25
27
  getValue: (data) => {
26
- return data.co && data.co.alarmStatus === 'active'
28
+ const coAlarmStatus = data.co?.alarmStatus ?? data.components?.['alarm.co']?.alarmStatus;
29
+ return coAlarmStatus === 'active'
27
30
  ? CarbonMonoxideDetected.CO_LEVELS_ABNORMAL
28
31
  : CarbonMonoxideDetected.CO_LEVELS_NORMAL;
29
32
  },
package/lib/switch.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class Switch extends BaseDeviceAccessory {
5
5
  device;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { distinctUntilChanged, map } from 'rxjs/operators';
4
4
  export class TemperatureSensor extends BaseDeviceAccessory {
5
5
  device;
package/lib/thermostat.js CHANGED
@@ -2,8 +2,8 @@ import { combineLatest } from 'rxjs';
2
2
  import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
3
3
  import { RingDeviceType } from 'ring-client-api';
4
4
  import { logDebug, logError, logInfo } from 'ring-client-api/util';
5
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
6
- import { hap } from "./hap.js";
5
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
6
+ import { hap } from './hap.js';
7
7
  export class Thermostat extends BaseDeviceAccessory {
8
8
  onTemperature;
9
9
  device;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class UnknownZWaveSwitchSwitch extends BaseDeviceAccessory {
5
5
  device;
package/lib/valve.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { logInfo } from 'ring-client-api/util';
4
4
  export class Valve extends BaseDeviceAccessory {
5
5
  device;
@@ -1,5 +1,5 @@
1
- import { BaseDeviceAccessory } from "./base-device-accessory.js";
2
- import { hap } from "./hap.js";
1
+ import { BaseDeviceAccessory } from './base-device-accessory.js';
2
+ import { hap } from './hap.js';
3
3
  import { distinctUntilChanged, filter, map } from 'rxjs/operators';
4
4
  import { logInfo } from 'ring-client-api/util';
5
5
  export class WaterSensor extends BaseDeviceAccessory {
package/package.json CHANGED
@@ -1,32 +1,33 @@
1
1
  {
2
2
  "name": "homebridge-ring",
3
3
  "displayName": "Ring",
4
- "version": "14.2.0",
4
+ "version": "15.0.0-beta.0",
5
5
  "description": "Homebridge plugin for Ring doorbells, cameras, security alarm system and smart lighting",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "scripts": {
9
- "build": "rm -rf lib && tsc && cp -r ./homebridge-ui/public ./lib/homebridge-ui/public/",
9
+ "build": "rm -rf lib && tsc && cp -r ./homebridge-ui/public ./lib/homebridge-ui/public/ && rm -rf ./lib/test",
10
10
  "lint": "eslint .",
11
- "dev": "concurrently -c yellow,blue --kill-others \"npm:dev:build\" \"npm:dev:run\" ",
12
- "dev:build": "tsc --watch --preserveWatchOutput",
13
- "dev:run": "RING_DEBUG=true node --watch ../../node_modules/.bin/homebridge -U ../../.homebridge"
11
+ "test": "vitest run",
12
+ "dev": "bash ./dev-homebridge.sh",
13
+ "dev:run": "npm run dev"
14
14
  },
15
15
  "dependencies": {
16
16
  "@homebridge/camera-utils": "^3.0.0",
17
- "@homebridge/plugin-ui-utils": "^2.1.0",
18
- "ring-client-api": "14.2.0",
19
- "werift": "0.22.2"
17
+ "@homebridge/plugin-ui-utils": "^2.2.7",
18
+ "ring-client-api": "15.0.0-beta.0",
19
+ "werift": "0.26.0"
20
20
  },
21
21
  "devDependencies": {
22
- "concurrently": "^9.2.1",
22
+ "@homebridge/hap-nodejs": "^2.3.0",
23
23
  "eslint-config-shared": "*",
24
- "homebridge": "1.11.1",
24
+ "homebridge": "2.5.0",
25
25
  "tsconfig": "*",
26
- "typescript": "5.9.3"
26
+ "typescript": "7.0.2",
27
+ "vitest": "^5.1.0"
27
28
  },
28
29
  "engines": {
29
- "node": "^20 || ^22 || ^24",
30
+ "node": "^22.19.0 || ^24 || ^26",
30
31
  "homebridge": ">=1.4.0 || ^2.0.0-beta.0"
31
32
  },
32
33
  "repository": {