ring-client-api 10.0.0-beta.3 → 10.0.0-beta.6

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
@@ -2,6 +2,52 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [10.0.0-beta.6](https://github.com/dgreif/ring/compare/v10.0.0-beta.5...v10.0.0-beta.6) (2022-05-06)
6
+
7
+
8
+ ### Features
9
+
10
+ * **api:** allow custom peer connections for streaming ([3e9e784](https://github.com/dgreif/ring/commit/3e9e78458afe2849a8c414a6b50eec8718bf77cc))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * update dependencies ([4b25595](https://github.com/dgreif/ring/commit/4b255950bcfbf94486192d55cfc90d966cd79d24))
16
+
17
+ ## [10.0.0-beta.5](https://github.com/dgreif/ring/compare/v10.0.0-beta.4...v10.0.0-beta.5) (2022-04-10)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **cameras:** wait to activate session until connected ([9a49389](https://github.com/dgreif/ring/commit/9a49389aa434cc5df4cfb4ba039bad0f2a30f756))
23
+ * handle push notification for unknown camera ([cc8fc2a](https://github.com/dgreif/ring/commit/cc8fc2a6377084515d3b169a85644b2a2b13235e))
24
+ * hide ECONNRESET errors from push receiver ([1196d96](https://github.com/dgreif/ring/commit/1196d96e67e26952f18dfbe75103419a7261bd1e))
25
+ * restore dual battery level calculations ([26fa84d](https://github.com/dgreif/ring/commit/26fa84dbd4bbcea8208fd0ade81c54c407aabf63))
26
+ * update dependencies ([4b97fb3](https://github.com/dgreif/ring/commit/4b97fb33623248147997944bab3de4ea3c2e5ee2))
27
+
28
+ ## [10.0.0-beta.4](https://github.com/dgreif/ring/compare/v10.0.0-beta.3...v10.0.0-beta.4) (2022-04-03)
29
+
30
+
31
+ ### ⚠ BREAKING CHANGES
32
+
33
+ * **api:** All ding-related functionality will now use the `PushNotification` type, and the `ActiveDing` type has been removed
34
+ * **api:** The `cameraDingsPollingSeconds` option has been removed. Ding events will now be received via push notifications (no setup required)
35
+ * **api:** The `treatKnockAsDing` option has been removed. Knocks should now register as "dings" automatically, though I have not been able to test this functionality
36
+
37
+ ### Features
38
+
39
+ * **api:** convert ding handling to use notifications ([5b62318](https://github.com/dgreif/ring/commit/5b62318b6a71bcc224a2b29cbd6e21162fc9891e))
40
+ * **api:** update notification types ([15d3e7e](https://github.com/dgreif/ring/commit/15d3e7e40744e2c3ed48140fa94fbc0c6f9d4088))
41
+ * **homebridge:** faster snapshots from camera notifications ([7fc821b](https://github.com/dgreif/ring/commit/7fc821b71013c36314d6d428b8152e631ea4b497))
42
+ * receive dings from push notifications ([c9f130f](https://github.com/dgreif/ring/commit/c9f130f0f1f21b6db2454d611641ef7c29c7e620))
43
+
44
+
45
+ ### Bug Fixes
46
+
47
+ * more closely match ring edge live streaming setup flow ([4bb3189](https://github.com/dgreif/ring/commit/4bb3189d6ce6614f7af64d04b410c3922957d555))
48
+ * update uuid namespace to force new client sessions ([bc3e943](https://github.com/dgreif/ring/commit/bc3e9432c41dda085845f72f332dcf480c92475d))
49
+ * use camera health to determine battery and charging status ([fac7033](https://github.com/dgreif/ring/commit/fac70331bdd4dd94bc6caa143eb1bcb9ead06923)), closes [#873](https://github.com/dgreif/ring/issues/873)
50
+
5
51
  ## [10.0.0-beta.3](https://github.com/dgreif/ring/compare/v10.0.0-beta.2...v10.0.0-beta.3) (2022-03-26)
6
52
 
7
53
 
package/README.md CHANGED
@@ -27,7 +27,6 @@ const ringApi = new RingApi({
27
27
 
28
28
  // The following are all optional. See below for details
29
29
  cameraStatusPollingSeconds: 20,
30
- cameraDingsPollingSeconds: 2,
31
30
  locationIds: ['488e4800-fcde-4493-969b-d1a06f683102', '4bbed7a7-06df-4f18-b3af-291c89854d60']
32
31
  });
33
32
  ```
@@ -37,7 +36,6 @@ const ringApi = new RingApi({
37
36
  Option | Default | Explanation
38
37
  --- | --- | ---
39
38
  `cameraStatusPollingSeconds` | `undefined` (No Polling) | How frequently to poll for updates to your cameras and chimes (in seconds). Information like light/siren/volume/snooze status do not update in real time and need to be requested periodically.
40
- `cameraDingsPollingSeconds` | `undefined` (No Polling) | How frequently to poll for new events from your cameras (in seconds). These include motion and doorbell presses. Without this option, cameras will not emit any information about motion and doorbell presses.
41
39
  `locationModePollingSeconds` | `undefined` (No Polling) | How frequently to poll for location mode updates (in seconds). This is only useful if you are using location modes to control camera settings and want to keep an up-to-date reference of the current mode for each location. Polling is automatically disabled for locations equipped with a Ring Alarm.
42
40
  `locationIds` | All Locations | Allows you to limit the results to a specific set of locations. This is mainly useful for the [homebridge-ring Plugin](./homebridge), but can also be used if you only care about listening for events at a subset of your locations and don't want to create websocket connections to _all_ of your locations. This will also limit the results for `ringApi.getCameras()` to the configured locations. If this option is not included, all locations will be returned.
43
41
  `debug` | false | Turns on additional logging. In particular, ffmpeg logging.
@@ -45,7 +43,6 @@ Option | Default | Explanation
45
43
  `ffmpegPath` | Uses `ffmpeg-for-homebridge` | A custom path to the `ffmpeg` executable. By default, the static binaries built in [ffmpeg-for-homebridge](https://github.com/oznu/ffmpeg-for-homebridg) will be used. If you prefer to use your own version of ffmpeg, you can pass a complete path, or simply `"ffmpeg"` to use ffmpeg from your `PATH`.
46
44
  `controlCenterDisplayName` | 'ring-client-api' | This allows you to change the displayed name for the Authorized Device within Control Center in the Ring app
47
45
  `avoidSnapshotBatteryDrain` | false | Causes snapshots for battery cameras to be fetched at a minimum 10 minute interval to avoid draining the battery.
48
- `treatKnockAsDing` | false | Will cause "knock" (`door_activity`) dings to be emitted from `onDoorbellPressed`
49
46
 
50
47
  ## Locations
51
48
  ```typescript
package/lib/api/api.d.ts CHANGED
@@ -1,15 +1,13 @@
1
1
  import { RefreshTokenAuth, RingRestClient, SessionOptions } from './rest-client';
2
2
  import { Location } from './location';
3
- import { ActiveDing, BaseStation, BeamBridge, CameraData, ChimeData, ProfileResponse, UserLocation } from './ring-types';
3
+ import { BaseStation, BeamBridge, CameraData, ChimeData, ProfileResponse, UserLocation } from './ring-types';
4
4
  import { RingCamera } from './ring-camera';
5
5
  import { Subscribed } from './subscribed';
6
6
  export interface RingApiOptions extends SessionOptions {
7
7
  locationIds?: string[];
8
8
  cameraStatusPollingSeconds?: number;
9
- cameraDingsPollingSeconds?: number;
10
9
  locationModePollingSeconds?: number;
11
10
  avoidSnapshotBatteryDrain?: boolean;
12
- treatKnockAsDing?: boolean;
13
11
  debug?: boolean;
14
12
  ffmpegPath?: string;
15
13
  externalPorts?: {
@@ -34,8 +32,8 @@ export declare class RingApi extends Subscribed {
34
32
  baseStations: BaseStation[];
35
33
  beamBridges: BeamBridge[];
36
34
  }>;
37
- fetchActiveDings(): Promise<ActiveDing[] & import("./rest-client").ExtendedResponse>;
38
35
  private listenForDeviceUpdates;
36
+ registerPushReceiver(cameras: RingCamera[]): Promise<void>;
39
37
  fetchRawLocations(): Promise<UserLocation[]>;
40
38
  fetchAmazonKeyLocks(): Promise<any[] & import("./rest-client").ExtendedResponse>;
41
39
  fetchAndBuildLocations(): Promise<Location[]>;
package/lib/api/api.js CHANGED
@@ -8,6 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.RingApi = void 0;
13
16
  const rest_client_1 = require("./rest-client");
@@ -19,6 +22,7 @@ const operators_1 = require("rxjs/operators");
19
22
  const util_1 = require("./util");
20
23
  const ffmpeg_1 = require("./ffmpeg");
21
24
  const subscribed_1 = require("./subscribed");
25
+ const push_receiver_1 = __importDefault(require("@eneris/push-receiver"));
22
26
  class RingApi extends subscribed_1.Subscribed {
23
27
  constructor(options) {
24
28
  super();
@@ -51,16 +55,13 @@ class RingApi extends subscribed_1.Subscribed {
51
55
  };
52
56
  });
53
57
  }
54
- fetchActiveDings() {
55
- return this.restClient.request({
56
- url: (0, rest_client_1.clientApi)('dings/active'),
57
- });
58
- }
59
58
  listenForDeviceUpdates(cameras, chimes) {
60
- const { cameraStatusPollingSeconds, cameraDingsPollingSeconds } = this.options, onCamerasRequestUpdate = (0, rxjs_1.merge)(...cameras.map((camera) => camera.onRequestUpdate)), onChimesRequestUpdate = (0, rxjs_1.merge)(...chimes.map((chime) => chime.onRequestUpdate)), onCamerasRequestActiveDings = (0, rxjs_1.merge)(...cameras.map((camera) => camera.onRequestActiveDings)), onUpdateReceived = new rxjs_1.Subject(), onActiveDingsReceived = new rxjs_1.Subject(), onPollForStatusUpdate = cameraStatusPollingSeconds
59
+ const { cameraStatusPollingSeconds } = this.options;
60
+ if (!cameraStatusPollingSeconds) {
61
+ return;
62
+ }
63
+ const onCamerasRequestUpdate = (0, rxjs_1.merge)(...cameras.map((camera) => camera.onRequestUpdate)), onChimesRequestUpdate = (0, rxjs_1.merge)(...chimes.map((chime) => chime.onRequestUpdate)), onUpdateReceived = new rxjs_1.Subject(), onPollForStatusUpdate = cameraStatusPollingSeconds
61
64
  ? onUpdateReceived.pipe((0, operators_1.debounceTime)(cameraStatusPollingSeconds * 1000))
62
- : rxjs_1.EMPTY, onPollForActiveDings = cameraDingsPollingSeconds
63
- ? onActiveDingsReceived.pipe((0, operators_1.debounceTime)(cameraDingsPollingSeconds * 1000))
64
65
  : rxjs_1.EMPTY, camerasById = cameras.reduce((byId, camera) => {
65
66
  byId[camera.id] = camera;
66
67
  return byId;
@@ -94,22 +95,59 @@ class RingApi extends subscribed_1.Subscribed {
94
95
  if (cameraStatusPollingSeconds) {
95
96
  onUpdateReceived.next(null); // kick off polling
96
97
  }
97
- this.addSubscriptions((0, rxjs_1.merge)(onCamerasRequestActiveDings, onPollForActiveDings).subscribe(() => __awaiter(this, void 0, void 0, function* () {
98
- const activeDings = yield this.fetchActiveDings().catch(() => null);
99
- onActiveDingsReceived.next(null);
100
- if (!activeDings || !activeDings.length) {
101
- return;
102
- }
103
- activeDings.forEach((activeDing) => {
104
- const camera = camerasById[activeDing.doorbot_id];
105
- if (camera) {
106
- camera.processActiveDing(activeDing);
98
+ }
99
+ registerPushReceiver(cameras) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ const pushReceiver = new push_receiver_1.default({
102
+ logLevel: 'NONE',
103
+ senderId: '876313859327', // for Ring android app. 703521446232 for ring-site
104
+ }), camerasById = cameras.reduce((byId, camera) => {
105
+ byId[camera.id] = camera;
106
+ return byId;
107
+ }, {});
108
+ pushReceiver.onCredentialsChanged(({ newCredentials: { fcm: { token }, }, }) => __awaiter(this, void 0, void 0, function* () {
109
+ try {
110
+ yield this.restClient.request({
111
+ url: (0, rest_client_1.clientApi)('device'),
112
+ method: 'PATCH',
113
+ json: {
114
+ device: {
115
+ metadata: {
116
+ pn_service: 'fcm',
117
+ },
118
+ os: 'android',
119
+ push_notification_token: token,
120
+ },
121
+ },
122
+ });
123
+ }
124
+ catch (e) {
125
+ (0, util_1.logError)(e);
126
+ }
127
+ }));
128
+ pushReceiver.onNotification(({ message }) => {
129
+ var _a, _b;
130
+ const dataJson = (_a = message.data) === null || _a === void 0 ? void 0 : _a.gcmData;
131
+ try {
132
+ const notification = JSON.parse(dataJson);
133
+ if (!notification.ding) {
134
+ // We may get notification that are not for a ding/motion, which can be ignored
135
+ return;
136
+ }
137
+ (_b = camerasById[notification.ding.doorbot_id]) === null || _b === void 0 ? void 0 : _b.processPushNotification(notification);
138
+ }
139
+ catch (e) {
140
+ (0, util_1.logError)(e);
107
141
  }
108
142
  });
109
- })));
110
- if (cameras.length && cameraDingsPollingSeconds) {
111
- onActiveDingsReceived.next(null); // kick off polling
112
- }
143
+ try {
144
+ yield pushReceiver.connect();
145
+ }
146
+ catch (e) {
147
+ (0, util_1.logError)('Failed to connect push notification receiver');
148
+ (0, util_1.logError)(e);
149
+ }
150
+ });
113
151
  }
114
152
  fetchRawLocations() {
115
153
  return __awaiter(this, void 0, void 0, function* () {
@@ -129,7 +167,7 @@ class RingApi extends subscribed_1.Subscribed {
129
167
  return __awaiter(this, void 0, void 0, function* () {
130
168
  const rawLocations = yield this.fetchRawLocations(), { authorizedDoorbots, chimes, doorbots, allCameras, baseStations, beamBridges, } = yield this.fetchRingDevices(), locationIdsWithHubs = [...baseStations, ...beamBridges].map((x) => x.location_id), cameras = allCameras.map((data) => new ring_camera_1.RingCamera(data, doorbots.includes(data) ||
131
169
  authorizedDoorbots.includes(data) ||
132
- data.kind.startsWith('doorbell'), this.restClient, this.options.avoidSnapshotBatteryDrain || false, this.options.treatKnockAsDing || false)), ringChimes = chimes.map((data) => new ring_chime_1.RingChime(data, this.restClient)), locations = rawLocations
170
+ data.kind.startsWith('doorbell'), this.restClient, this.options.avoidSnapshotBatteryDrain || false)), ringChimes = chimes.map((data) => new ring_chime_1.RingChime(data, this.restClient)), locations = rawLocations
133
171
  .filter((location) => {
134
172
  return (!Array.isArray(this.options.locationIds) ||
135
173
  this.options.locationIds.includes(location.location_id));
@@ -140,6 +178,9 @@ class RingApi extends subscribed_1.Subscribed {
140
178
  locationModePollingSeconds: this.options.locationModePollingSeconds,
141
179
  }, this.restClient));
142
180
  this.listenForDeviceUpdates(cameras, ringChimes);
181
+ this.registerPushReceiver(cameras).catch((e) => {
182
+ (0, util_1.logError)(e);
183
+ });
143
184
  return locations;
144
185
  });
145
186
  }
package/lib/api/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -1,12 +1,14 @@
1
1
  /// <reference types="node" />
2
- import { ActiveDing, CameraData, CameraDeviceSettingsData, CameraEventOptions, CameraEventResponse, CameraHealth, HistoryOptions, PeriodicFootageResponse, VideoSearchResponse } from './ring-types';
2
+ import { CameraData, CameraDeviceSettingsData, CameraEventOptions, CameraEventResponse, CameraHealth, HistoryOptions, PeriodicFootageResponse, PushNotification, VideoSearchResponse } from './ring-types';
3
3
  import { RingRestClient } from './rest-client';
4
4
  import { BehaviorSubject, Subject } from 'rxjs';
5
5
  import { DeepPartial } from './util';
6
6
  import { Subscribed } from './subscribed';
7
- import { FfmpegOptions, LiveCall } from './live-call';
8
- import { LiveCallRingEdge } from './live-call-ring-edge';
9
- export declare function getBatteryLevel(data: Pick<CameraData, 'battery_life' | 'battery_life_2'>): number | null;
7
+ import { FfmpegOptions, StreamingSession } from './streaming/streaming-session';
8
+ import { StreamingConnectionOptions } from './streaming/streaming-connection-base';
9
+ export declare function getBatteryLevel(data: Pick<CameraData, 'battery_life' | 'battery_life_2'> & {
10
+ health?: Partial<CameraData['health']>;
11
+ }): number | null;
10
12
  export declare function getSearchQueryString(options: CameraEventOptions | (HistoryOptions & {
11
13
  accountId: string;
12
14
  })): string;
@@ -15,7 +17,6 @@ export declare class RingCamera extends Subscribed {
15
17
  isDoorbot: boolean;
16
18
  private restClient;
17
19
  private avoidSnapshotBatteryDrain;
18
- private treatKnockAsDing;
19
20
  id: number;
20
21
  deviceType: import("./ring-types").RingCameraKind;
21
22
  model: string;
@@ -23,20 +24,20 @@ export declare class RingCamera extends Subscribed {
23
24
  hasLight: boolean;
24
25
  hasSiren: boolean;
25
26
  onRequestUpdate: Subject<unknown>;
26
- onRequestActiveDings: Subject<unknown>;
27
- onNewDing: Subject<ActiveDing>;
28
- onActiveDings: BehaviorSubject<ActiveDing[]>;
29
- onDoorbellPressed: import("rxjs").Observable<ActiveDing>;
27
+ onNewNotification: Subject<PushNotification>;
28
+ onActiveNotifications: BehaviorSubject<PushNotification[]>;
29
+ onDoorbellPressed: import("rxjs").Observable<PushNotification>;
30
30
  onMotionDetected: import("rxjs").Observable<boolean>;
31
31
  onMotionStarted: import("rxjs").Observable<null>;
32
32
  onBatteryLevel: import("rxjs").Observable<number | null>;
33
33
  onInHomeDoorbellStatus: import("rxjs").Observable<boolean>;
34
- constructor(initialData: CameraData, isDoorbot: boolean, restClient: RingRestClient, avoidSnapshotBatteryDrain: boolean, treatKnockAsDing: boolean);
34
+ constructor(initialData: CameraData, isDoorbot: boolean, restClient: RingRestClient, avoidSnapshotBatteryDrain: boolean);
35
35
  updateData(update: CameraData): void;
36
36
  requestUpdate(): void;
37
37
  get data(): CameraData;
38
38
  get name(): string;
39
- get activeDings(): ActiveDing[];
39
+ get activeNotifications(): PushNotification[];
40
+ get latestNotification(): PushNotification | undefined;
40
41
  get batteryLevel(): number | null;
41
42
  get hasBattery(): boolean;
42
43
  get hasLowBattery(): boolean;
@@ -54,9 +55,10 @@ export declare class RingCamera extends Subscribed {
54
55
  getDeviceSettings(): Promise<CameraDeviceSettingsData & import("./rest-client").ExtendedResponse>;
55
56
  setInHomeDoorbell(enable: boolean): Promise<boolean>;
56
57
  getHealth(): Promise<CameraHealth>;
57
- startLiveCall(): Promise<LiveCall | LiveCallRingEdge>;
58
+ private createStreamingConnection;
59
+ startLiveCall(options?: StreamingConnectionOptions): Promise<StreamingSession>;
58
60
  private removeDingById;
59
- processActiveDing(ding: ActiveDing): void;
61
+ processPushNotification(notification: PushNotification): void;
60
62
  getEvents(options?: CameraEventOptions): Promise<CameraEventResponse & import("./rest-client").ExtendedResponse>;
61
63
  videoSearch({ dateFrom, dateTo, order }?: {
62
64
  dateFrom: number;
@@ -81,14 +83,18 @@ export declare class RingCamera extends Subscribed {
81
83
  private checkIfSnapshotsAreBlocked;
82
84
  private shouldUseExistingSnapshotPromise;
83
85
  private fetchingSnapshot;
84
- getSnapshot(): Promise<Buffer>;
85
- getNextSnapshot({ afterMs, maxWaitMs, force, }: {
86
+ getSnapshot({ uuid }?: {
87
+ uuid?: string;
88
+ }): Promise<Buffer>;
89
+ getNextSnapshot({ afterMs, maxWaitMs, force, uuid, }: {
86
90
  afterMs?: number;
87
91
  maxWaitMs?: number;
88
92
  force?: boolean;
93
+ uuid?: string;
89
94
  }): Promise<Buffer & import("./rest-client").ExtendedResponse>;
95
+ getSnapshotByUuid(uuid: string): Promise<Buffer & import("./rest-client").ExtendedResponse>;
90
96
  recordToFile(outputPath: string, duration?: number): Promise<void>;
91
- streamVideo(ffmpegOptions: FfmpegOptions): Promise<LiveCall | LiveCallRingEdge>;
97
+ streamVideo(ffmpegOptions: FfmpegOptions): Promise<StreamingSession>;
92
98
  subscribeToDingEvents(): Promise<void & import("./rest-client").ExtendedResponse>;
93
99
  unsubscribeFromDingEvents(): Promise<void & import("./rest-client").ExtendedResponse>;
94
100
  subscribeToMotionEvents(): Promise<void & import("./rest-client").ExtendedResponse>;
@@ -16,8 +16,9 @@ const rxjs_1 = require("rxjs");
16
16
  const operators_1 = require("rxjs/operators");
17
17
  const util_1 = require("./util");
18
18
  const subscribed_1 = require("./subscribed");
19
- const live_call_1 = require("./live-call");
20
- const live_call_ring_edge_1 = require("./live-call-ring-edge");
19
+ const webrtc_connection_1 = require("./streaming/webrtc-connection");
20
+ const ring_edge_connection_1 = require("./streaming/ring-edge-connection");
21
+ const streaming_session_1 = require("./streaming/streaming-session");
21
22
  const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
22
23
  function parseBatteryLife(batteryLife) {
23
24
  if (batteryLife === null || batteryLife === undefined) {
@@ -41,8 +42,9 @@ function getBatteryLevel(data) {
41
42
  const levels = [
42
43
  parseBatteryLife(data.battery_life),
43
44
  parseBatteryLife(data.battery_life_2),
44
- ].filter((level) => level !== null);
45
- if (!levels.length) {
45
+ ].filter((level) => level !== null), { health } = data;
46
+ if (!levels.length ||
47
+ (health && !health.battery_percentage && !health.battery_present)) {
46
48
  return null;
47
49
  }
48
50
  return Math.min(...levels);
@@ -65,20 +67,17 @@ function getSearchQueryString(options) {
65
67
  }
66
68
  exports.getSearchQueryString = getSearchQueryString;
67
69
  class RingCamera extends subscribed_1.Subscribed {
68
- constructor(initialData, isDoorbot, restClient, avoidSnapshotBatteryDrain, treatKnockAsDing) {
70
+ constructor(initialData, isDoorbot, restClient, avoidSnapshotBatteryDrain) {
69
71
  super();
70
72
  this.initialData = initialData;
71
73
  this.isDoorbot = isDoorbot;
72
74
  this.restClient = restClient;
73
75
  this.avoidSnapshotBatteryDrain = avoidSnapshotBatteryDrain;
74
- this.treatKnockAsDing = treatKnockAsDing;
75
76
  this.onRequestUpdate = new rxjs_1.Subject();
76
- this.onRequestActiveDings = new rxjs_1.Subject();
77
- this.onNewDing = new rxjs_1.Subject();
78
- this.onActiveDings = new rxjs_1.BehaviorSubject([]);
79
- this.onDoorbellPressed = this.onNewDing.pipe((0, operators_1.filter)((ding) => ding.kind === 'ding' ||
80
- (this.treatKnockAsDing && ding.kind === 'door_activity')), (0, operators_1.share)());
81
- this.onMotionDetected = this.onActiveDings.pipe((0, operators_1.map)((dings) => dings.some((ding) => ding.motion || ding.kind === 'motion')), (0, operators_1.distinctUntilChanged)(), (0, operators_1.publishReplay)(1), (0, operators_1.refCount)());
77
+ this.onNewNotification = new rxjs_1.Subject();
78
+ this.onActiveNotifications = new rxjs_1.BehaviorSubject([]);
79
+ this.onDoorbellPressed = this.onNewNotification.pipe((0, operators_1.filter)((notification) => notification.action === ring_types_1.PushNotificationAction.Ding), (0, operators_1.share)());
80
+ this.onMotionDetected = this.onActiveNotifications.pipe((0, operators_1.map)((notifications) => notifications.some((notification) => notification.action === ring_types_1.PushNotificationAction.Motion)), (0, operators_1.distinctUntilChanged)(), (0, operators_1.publishReplay)(1), (0, operators_1.refCount)());
82
81
  this.onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.mapTo)(null), // no value needed, event is what matters
83
82
  (0, operators_1.share)());
84
83
  this.lastSnapshotTimestamp = 0;
@@ -119,20 +118,18 @@ class RingCamera extends subscribed_1.Subscribed {
119
118
  get name() {
120
119
  return this.data.description;
121
120
  }
122
- get activeDings() {
123
- return this.onActiveDings.getValue();
121
+ get activeNotifications() {
122
+ return this.onActiveNotifications.getValue();
123
+ }
124
+ get latestNotification() {
125
+ const notifications = this.activeNotifications;
126
+ return notifications[notifications.length - 1];
124
127
  }
125
128
  get batteryLevel() {
126
129
  return getBatteryLevel(this.data);
127
130
  }
128
131
  get hasBattery() {
129
- if (this.batteryLevel === null) {
130
- return false;
131
- }
132
- return ((0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
133
- (typeof this.initialData.battery_life === 'string' &&
134
- this.batteryLevel < 100 &&
135
- this.batteryLevel >= 0));
132
+ return this.batteryLevel !== null;
136
133
  }
137
134
  get hasLowBattery() {
138
135
  return this.data.alerts.battery === 'low';
@@ -233,11 +230,11 @@ class RingCamera extends subscribed_1.Subscribed {
233
230
  return response.device_health;
234
231
  });
235
232
  }
236
- startLiveCall() {
233
+ createStreamingConnection(options) {
237
234
  return __awaiter(this, void 0, void 0, function* () {
238
235
  if (this.isRingEdgeEnabled) {
239
236
  const auth = yield this.restClient.getCurrentAuth();
240
- return new live_call_ring_edge_1.LiveCallRingEdge(auth.access_token, this);
237
+ return new ring_edge_connection_1.RingEdgeConnection(auth.access_token, this, options);
241
238
  }
242
239
  const liveCall = yield this.restClient
243
240
  .request({
@@ -253,19 +250,25 @@ class RingCamera extends subscribed_1.Subscribed {
253
250
  }
254
251
  throw e;
255
252
  });
256
- return new live_call_1.LiveCall(liveCall.data.session_id, this);
253
+ return new webrtc_connection_1.WebrtcConnection(liveCall.data.session_id, this, options);
254
+ });
255
+ }
256
+ startLiveCall(options = {}) {
257
+ return __awaiter(this, void 0, void 0, function* () {
258
+ const connection = yield this.createStreamingConnection(options);
259
+ return new streaming_session_1.StreamingSession(this, connection);
257
260
  });
258
261
  }
259
262
  removeDingById(idToRemove) {
260
- const allActiveDings = this.activeDings, otherDings = allActiveDings.filter((ding) => ding.id_str !== idToRemove);
261
- this.onActiveDings.next(otherDings);
263
+ const allActiveDings = this.activeNotifications, otherDings = allActiveDings.filter(({ ding }) => ding.id !== idToRemove);
264
+ this.onActiveNotifications.next(otherDings);
262
265
  }
263
- processActiveDing(ding) {
264
- const activeDings = this.activeDings, dingId = ding.id_str;
265
- this.onActiveDings.next(activeDings.filter((d) => d.id_str !== dingId).concat([ding]));
266
- this.onNewDing.next(ding);
266
+ processPushNotification(notification) {
267
+ const activeDings = this.activeNotifications, dingId = notification.ding.id;
268
+ this.onActiveNotifications.next(activeDings.filter((d) => d.ding.id !== dingId).concat([notification]));
269
+ this.onNewNotification.next(notification);
267
270
  setTimeout(() => {
268
- this.removeDingById(ding.id_str);
271
+ this.removeDingById(dingId);
269
272
  }, 65 * 1000); // dings last ~1 minute
270
273
  }
271
274
  getEvents(options = {}) {
@@ -337,17 +340,19 @@ class RingCamera extends subscribed_1.Subscribed {
337
340
  return false;
338
341
  }
339
342
  }
340
- getSnapshot() {
343
+ getSnapshot({ uuid } = {}) {
341
344
  return __awaiter(this, void 0, void 0, function* () {
342
345
  if (this.lastSnapshotPromise && this.shouldUseExistingSnapshotPromise()) {
343
346
  return this.lastSnapshotPromise;
344
347
  }
345
348
  this.checkIfSnapshotsAreBlocked();
346
349
  this.lastSnapshotPromise = Promise.race([
347
- this.getNextSnapshot({
348
- afterMs: this.lastSnapshotTimestamp,
349
- force: true,
350
- }),
350
+ this.getNextSnapshot(uuid
351
+ ? { uuid }
352
+ : {
353
+ afterMs: this.lastSnapshotTimestamp,
354
+ force: true,
355
+ }),
351
356
  (0, util_1.delay)(maxSnapshotRefreshSeconds * 1000).then(() => {
352
357
  const extraMessageForBatteryCam = this.operatingOnBattery
353
358
  ? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
@@ -367,7 +372,7 @@ class RingCamera extends subscribed_1.Subscribed {
367
372
  return this.lastSnapshotPromise;
368
373
  });
369
374
  }
370
- getNextSnapshot({ afterMs, maxWaitMs, force, }) {
375
+ getNextSnapshot({ afterMs, maxWaitMs, force, uuid, }) {
371
376
  return __awaiter(this, void 0, void 0, function* () {
372
377
  const response = yield this.restClient.request({
373
378
  url: `https://app-snaps.ring.com/snapshots/next/${this.id}`,
@@ -376,6 +381,7 @@ class RingCamera extends subscribed_1.Subscribed {
376
381
  'after-ms': afterMs,
377
382
  'max-wait-ms': maxWaitMs,
378
383
  extras: force ? 'force' : undefined,
384
+ uuid,
379
385
  },
380
386
  headers: {
381
387
  accept: 'image/jpeg',
@@ -386,6 +392,15 @@ class RingCamera extends subscribed_1.Subscribed {
386
392
  return response;
387
393
  });
388
394
  }
395
+ getSnapshotByUuid(uuid) {
396
+ return this.restClient.request({
397
+ url: (0, rest_client_1.clientApi)('snapshots/uuid?uuid=' + uuid),
398
+ responseType: 'buffer',
399
+ headers: {
400
+ accept: 'image/jpeg',
401
+ },
402
+ });
403
+ }
389
404
  recordToFile(outputPath, duration = 30) {
390
405
  return __awaiter(this, void 0, void 0, function* () {
391
406
  const liveCall = yield this.streamVideo({
@@ -87,7 +87,6 @@ export declare enum RingCameraKind {
87
87
  cocoa_doorbell = "cocoa_doorbell",
88
88
  stickup_cam_mini = "stickup_cam_mini"
89
89
  }
90
- export declare function isBatteryCameraKind(kind: RingCameraKind): boolean;
91
90
  export declare const RingCameraModel: {
92
91
  readonly [P in RingCameraKind]: string;
93
92
  };
@@ -427,6 +426,44 @@ export interface CameraData {
427
426
  ends_at?: string;
428
427
  seconds_remaining: number;
429
428
  };
429
+ health: {
430
+ device_type: RingCameraKind;
431
+ last_update_time: number;
432
+ connected: boolean;
433
+ rss_connected: boolean;
434
+ vod_enabled: boolean;
435
+ sidewalk_connection: boolean;
436
+ floodlight_on: boolean;
437
+ siren_on?: boolean;
438
+ white_led_on: boolean;
439
+ night_mode_on: boolean;
440
+ hatch_open: boolean;
441
+ packet_loss: number;
442
+ packet_loss_category: 'good' | string;
443
+ rssi: number;
444
+ battery_voltage: number;
445
+ wifi_is_ring_network: boolean;
446
+ supported_rpc_commands: string[];
447
+ ota_status: 'successful' | string;
448
+ ext_power_state?: 0 | number;
449
+ pref_run_mode?: 'low_power' | string;
450
+ run_mode?: 'low_power' | 'full_power' | string;
451
+ network_connection_value: 'wifi' | string;
452
+ ac_power: number;
453
+ battery_present?: boolean;
454
+ external_connection: boolean;
455
+ battery_percentage?: number;
456
+ battery_percentage_category: 'very_good' | 'unknown' | string;
457
+ firmware_version: string;
458
+ rssi_category: 'good' | string;
459
+ battery_voltage_category: 'very_good' | string;
460
+ second_battery_voltage_category: 'unknown' | string;
461
+ second_battery_percentage_category: 'unknown' | string;
462
+ battery_save: boolean;
463
+ firmware_version_status: 'Up to Date';
464
+ tx_rate: number;
465
+ ptz_connected?: 'penguin';
466
+ };
430
467
  }
431
468
  export interface CvDetectionType {
432
469
  enabled: boolean;
@@ -670,31 +707,44 @@ export interface RingDeviceHistoryEvent {
670
707
  body: any;
671
708
  }
672
709
  export declare type DingState = 'ringing' | 'connected' | 'timed_out' | 'completed';
673
- export interface ActiveDing {
674
- id: number;
675
- id_str: string;
676
- state: DingState;
677
- protocol: 'sip';
678
- doorbot_id: number;
679
- doorbot_description: string;
680
- device_kind: RingCameraKind;
681
- motion: boolean;
682
- snapshot_url: '';
683
- kind: DingKind;
684
- sip_server_ip: string;
685
- sip_server_port: number;
686
- sip_server_tls: boolean;
687
- sip_session_id: string;
688
- sip_from: string;
689
- sip_to: string;
690
- audio_jitter_buffer_ms: number;
691
- video_jitter_buffer_ms: number;
692
- sip_endpoints: null;
693
- expires_in: number;
694
- now: number;
695
- optimization_level: number;
696
- sip_token: string;
697
- sip_ding_id: string;
710
+ export declare enum NotificationDetectionType {
711
+ Human = "human",
712
+ Loitering = "loitering",
713
+ Motion = "motion",
714
+ OtherMotion = "other_motion",
715
+ NotAvailable = "null",
716
+ StreamBroken = "stream_broken"
717
+ }
718
+ export declare enum PushNotificationAction {
719
+ Ding = "com.ring.push.HANDLE_NEW_DING",
720
+ Motion = "com.ring.push.HANDLE_NEW_motion"
721
+ }
722
+ export interface PushNotification {
723
+ ding: {
724
+ streaming_protocol: 'ring_media_server';
725
+ location_id: string;
726
+ device_name: string;
727
+ doorbot_id: number;
728
+ e2ee_enabled: boolean;
729
+ streaming_data_hash: string;
730
+ device_kind: RingCameraKind;
731
+ detection_type: NotificationDetectionType;
732
+ human_detected?: boolean;
733
+ id: number;
734
+ pod_id: number;
735
+ request_id: string;
736
+ image_uuid: string;
737
+ properties: {
738
+ active_streaming_profile: 'rms';
739
+ is_sidewalk: boolean;
740
+ };
741
+ };
742
+ aps: {
743
+ alert: string;
744
+ sound: string;
745
+ };
746
+ subtype: 'motion' | 'ding' | 'human' | string;
747
+ action: PushNotificationAction | string;
698
748
  }
699
749
  export interface LiveCallResponse {
700
750
  ding_id: string;