ring-client-api 13.0.0-beta.0 → 13.0.0-beta.2

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/lib/api.js CHANGED
@@ -200,7 +200,14 @@ class RingApi extends subscribed_1.Subscribed {
200
200
  messageData[p] = message.data[p];
201
201
  }
202
202
  }
203
- const notification = messageData, deviceId = notification.data?.device?.id;
203
+ const notification = messageData;
204
+ if (!('android_config' in notification)) {
205
+ // This is not a v2 ding style notification, so we can't process it
206
+ (0, util_1.logInfo)('Received push notification in unknown format');
207
+ (0, util_1.logInfo)(JSON.stringify(message));
208
+ return;
209
+ }
210
+ const deviceId = notification.data?.device?.id;
204
211
  if (deviceId) {
205
212
  sendToDevice(deviceId, notification);
206
213
  }
@@ -1,7 +1,11 @@
1
- import { Options as RequestOptions } from 'got';
2
1
  import { AuthTokenResponse, SessionResponse } from './ring-types';
3
2
  import { ReplaySubject } from 'rxjs';
4
3
  import type { Credentials } from '@eneris/push-receiver/dist/types';
4
+ interface RequestOptions extends RequestInit {
5
+ responseType?: 'json' | 'buffer';
6
+ timeout?: number;
7
+ json?: object;
8
+ }
5
9
  export declare function clientApi(path: string): string;
6
10
  export declare function deviceApi(path: string): string;
7
11
  export declare function commandsApi(path: string): string;
@@ -59,3 +63,4 @@ export declare class RingRestClient {
59
63
  get _internalOnly_pushNotificationCredentials(): Credentials | undefined;
60
64
  set _internalOnly_pushNotificationCredentials(credentials: Credentials | undefined);
61
65
  }
66
+ export {};
@@ -8,14 +8,12 @@ exports.clientApi = clientApi;
8
8
  exports.deviceApi = deviceApi;
9
9
  exports.commandsApi = commandsApi;
10
10
  exports.appApi = appApi;
11
- const got_1 = __importDefault(require("got"));
12
11
  const util_1 = require("./util");
13
12
  const rxjs_1 = require("rxjs");
14
13
  const assert_1 = __importDefault(require("assert"));
15
14
  const defaultRequestOptions = {
16
15
  responseType: 'json',
17
16
  method: 'GET',
18
- retry: 0,
19
17
  timeout: 20000,
20
18
  }, ringErrorCodes = {
21
19
  7050: 'NO_ASSET',
@@ -36,18 +34,76 @@ function commandsApi(path) {
36
34
  function appApi(path) {
37
35
  return appApiBaseUrl + path;
38
36
  }
37
+ async function responseToError(response) {
38
+ const error = new Error();
39
+ error.response = {
40
+ headers: response.headers,
41
+ status: response.status,
42
+ body: null,
43
+ };
44
+ try {
45
+ const bodyText = await response.text();
46
+ try {
47
+ error.response.body = JSON.parse(bodyText);
48
+ }
49
+ catch (_) {
50
+ error.response.body = bodyText;
51
+ }
52
+ }
53
+ catch (_) {
54
+ // ignore
55
+ }
56
+ return error;
57
+ }
39
58
  async function requestWithRetry(requestOptions) {
40
59
  try {
60
+ if (requestOptions.json || requestOptions.responseType === 'json') {
61
+ requestOptions.headers = {
62
+ ...requestOptions.headers,
63
+ 'Content-Type': 'application/json',
64
+ Accept: 'application/json',
65
+ };
66
+ if (requestOptions.json) {
67
+ requestOptions.body = JSON.stringify(requestOptions.json);
68
+ }
69
+ delete requestOptions.json;
70
+ }
41
71
  const options = {
42
72
  ...defaultRequestOptions,
43
73
  ...requestOptions,
44
- }, { headers, body } = (await (0, got_1.default)(options)), data = body;
74
+ };
75
+ // If a timeout is provided, create an AbortSignal for it
76
+ if (options.timeout && !options.signal) {
77
+ options.signal = AbortSignal.timeout(options.timeout);
78
+ }
79
+ // make the fetch request
80
+ const response = await fetch(options.url, options), headers = response.headers;
81
+ if (!response.ok) {
82
+ const error = await responseToError(response);
83
+ throw error;
84
+ }
85
+ let data;
86
+ if (options.responseType === 'buffer') {
87
+ const arrayBuffer = await response.arrayBuffer();
88
+ data = Buffer.from(arrayBuffer);
89
+ }
90
+ else {
91
+ const text = await response.text();
92
+ try {
93
+ data = JSON.parse(text);
94
+ }
95
+ catch (_) {
96
+ data = text;
97
+ }
98
+ }
45
99
  if (data !== null && typeof data === 'object') {
46
- if (headers.date) {
47
- data.responseTimestamp = new Date(headers.date).getTime();
100
+ const date = headers.get('date');
101
+ if (date) {
102
+ data.responseTimestamp = new Date(date).getTime();
48
103
  }
49
- if (headers['x-time-millis']) {
50
- data.timeMillis = Number(headers['x-time-millis']);
104
+ const xTime = headers.get('x-time-millis');
105
+ if (xTime) {
106
+ data.timeMillis = Number(xTime);
51
107
  }
52
108
  }
53
109
  return data;
@@ -186,12 +242,12 @@ class RingRestClient {
186
242
  const response = requestError.response || {}, responseData = response.body || {}, responseError = 'error' in responseData && typeof responseData.error === 'string'
187
243
  ? responseData.error
188
244
  : '';
189
- if (response.statusCode === 412 || // need 2fa code
190
- (response.statusCode === 400 &&
245
+ if (response.status === 412 || // need 2fa code
246
+ (response.status === 400 &&
191
247
  responseError.startsWith('Verification Code')) // invalid 2fa code entered
192
248
  ) {
193
249
  this.using2fa = true;
194
- if (response.statusCode === 400) {
250
+ if (response.status === 400) {
195
251
  this.promptFor2fa = 'Invalid 2fa code entered. Please try again.';
196
252
  throw new Error(responseError);
197
253
  }
@@ -245,12 +301,12 @@ class RingRestClient {
245
301
  }
246
302
  catch (e) {
247
303
  const response = e.response || {};
248
- if (response.statusCode === 401) {
304
+ if (response.status === 401) {
249
305
  await this.refreshAuth();
250
306
  return this.getSession();
251
307
  }
252
- if (response.statusCode === 429) {
253
- const retryAfter = e.response.headers['retry-after'], waitSeconds = isNaN(retryAfter)
308
+ if (response.status === 429) {
309
+ const retryAfter = e.response.headers.get('retry-after'), waitSeconds = isNaN(retryAfter)
254
310
  ? 200
255
311
  : Number.parseInt(retryAfter, 10);
256
312
  (0, util_1.logError)(`Session response rate limited. Waiting to retry after ${waitSeconds} seconds`);
@@ -297,16 +353,16 @@ class RingRestClient {
297
353
  }
298
354
  catch (e) {
299
355
  const response = e.response || {};
300
- if (response.statusCode === 401) {
356
+ if (response.status === 401) {
301
357
  await this.refreshAuth();
302
358
  return this.request(options);
303
359
  }
304
- if (response.statusCode === 504) {
360
+ if (response.status === 504) {
305
361
  // Gateway Timeout. These should be recoverable, but wait a few seconds just to be on the safe side
306
362
  await (0, util_1.delay)(5000);
307
363
  return this.request(options);
308
364
  }
309
- if (response.statusCode === 404 &&
365
+ if (response.status === 404 &&
310
366
  response.body &&
311
367
  Array.isArray(response.body.errors)) {
312
368
  const errors = response.body.errors, errorText = errors
@@ -320,7 +376,7 @@ class RingRestClient {
320
376
  }
321
377
  (0, util_1.logError)(`http request failed. ${url} returned unknown errors: (${(0, util_1.stringify)(errors)}).`);
322
378
  }
323
- if (response.statusCode === 404 && url.startsWith(clientApiBaseUrl)) {
379
+ if (response.status === 404 && url.startsWith(clientApiBaseUrl)) {
324
380
  (0, util_1.logError)('404 from endpoint ' + url);
325
381
  if (response.body?.error?.includes(hardwareId)) {
326
382
  (0, util_1.logError)('Session hardware_id not found. Creating a new session and trying again.');
@@ -331,8 +387,8 @@ class RingRestClient {
331
387
  }
332
388
  throw new Error('Not found with response: ' + (0, util_1.stringify)(response.body));
333
389
  }
334
- if (response.statusCode) {
335
- (0, util_1.logError)(`Request to ${url} failed with status ${response.statusCode}. Response body: ${(0, util_1.stringify)(response.body)}`);
390
+ if (response.status) {
391
+ (0, util_1.logError)(`Request to ${url} failed with status ${response.status}. Response body: ${(0, util_1.stringify)(response.body)}`);
336
392
  }
337
393
  else if (!options.allowNoResponse) {
338
394
  (0, util_1.logError)(`Request to ${url} failed:`);
@@ -1,4 +1,4 @@
1
- import { CameraData, CameraDeviceSettingsData, CameraEventOptions, CameraEventResponse, CameraHealth, HistoryOptions, PeriodicFootageResponse, PushNotification, PushNotificationDingV2, VideoSearchResponse, OnvifCameraData, RingCameraKind } from './ring-types';
1
+ import { CameraData, CameraDeviceSettingsData, CameraEventOptions, CameraEventResponse, CameraHealth, HistoryOptions, PeriodicFootageResponse, PushNotificationDingV2, VideoSearchResponse, OnvifCameraData, RingCameraKind } from './ring-types';
2
2
  import { RingRestClient } from './rest-client';
3
3
  import { BehaviorSubject, Subject } from 'rxjs';
4
4
  import { DeepPartial } from './util';
@@ -61,7 +61,7 @@ export declare class RingCamera extends Subscribed {
61
61
  private createStreamingConnection;
62
62
  startLiveCall(options?: StreamingConnectionOptions): Promise<StreamingSession>;
63
63
  private removeDingById;
64
- processPushNotification(notification: PushNotification): void;
64
+ processPushNotification(notification: PushNotificationDingV2): void;
65
65
  getEvents(options?: CameraEventOptions): Promise<CameraEventResponse & import("./rest-client").ExtendedResponse>;
66
66
  videoSearch({ dateFrom, dateTo, order }?: {
67
67
  dateFrom: number;
@@ -374,14 +374,13 @@ class RingCamera extends subscribed_1.Subscribed {
374
374
  }
375
375
  async getNextSnapshot({ afterMs, maxWaitMs, force, uuid, }) {
376
376
  const response = await this.restClient.request({
377
- url: `https://app-snaps.ring.com/snapshots/next/${this.id}`,
378
- responseType: 'buffer',
379
- searchParams: {
377
+ url: `https://app-snaps.ring.com/snapshots/next/${this.id}${(0, util_1.buildSearchString)({
380
378
  'after-ms': afterMs,
381
379
  'max-wait-ms': maxWaitMs,
382
380
  extras: force ? 'force' : undefined,
383
381
  uuid: cleanSnapshotUuid(uuid),
384
- },
382
+ })}`,
383
+ responseType: 'buffer',
385
384
  headers: {
386
385
  accept: 'image/jpeg',
387
386
  },
@@ -1,4 +1,4 @@
1
- import { IntercomHandsetAudioData, PushNotification } from './ring-types';
1
+ import { IntercomHandsetAudioData, PushNotificationDingV2 } from './ring-types';
2
2
  import { RingRestClient } from './rest-client';
3
3
  import { BehaviorSubject, Subject } from 'rxjs';
4
4
  export declare class RingIntercom {
@@ -22,5 +22,5 @@ export declare class RingIntercom {
22
22
  private doorbotUrl;
23
23
  subscribeToDingEvents(): Promise<void & import("./rest-client").ExtendedResponse>;
24
24
  unsubscribeFromDingEvents(): Promise<void & import("./rest-client").ExtendedResponse>;
25
- processPushNotification(notification: PushNotification): void;
25
+ processPushNotification(notification: PushNotificationDingV2): void;
26
26
  }
@@ -867,8 +867,11 @@ export declare enum NotificationDetectionType {
867
867
  export declare enum PushNotificationAction {
868
868
  Ding = "com.ring.pn.live-event.ding",
869
869
  Motion = "com.ring.pn.live-event.motion",
870
- IntercomUnlock = "com.ring.pn.live-event.intercom",
871
- LowBattery = "com.ring.push.LOW_BATTERY_ALERT"
870
+ IntercomUnlock = "com.ring.pn.intercom.virtual.unlock",
871
+ AlarmModeNone = "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_NONE_NOTICE",
872
+ AlarmModeSome = "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_SOME_NOTICE",
873
+ AlarmSoundSiren = "com.ring.push.HANDLE_NEW_USER_SOUND_SIREN",
874
+ AlarmSilenceSiren = "com.ring.push.HANDLE_NEW_NON_ALARM_SIREN_SILENCED"
872
875
  }
873
876
  export interface PushNotificationDingV2 {
874
877
  version: '2.0.0' | string;
@@ -930,20 +933,12 @@ export interface PushNotificationAlarm {
930
933
  location_id: string;
931
934
  };
932
935
  }
933
- export interface PushNotificationLowBattery {
936
+ export interface PushNotificationAlarmV2 {
934
937
  data: {
935
- device_name: string;
936
- doorbot_id: number;
937
- battery_level: number;
938
- device_kind: RingDeviceType;
939
- timestamp_epoch_ms: number;
938
+ gcmData: PushNotificationAlarm;
940
939
  };
941
- aps: {
942
- title: string;
943
- };
944
- action: PushNotificationAction.LowBattery;
945
940
  }
946
- export type PushNotification = PushNotificationDingV2;
941
+ export type PushNotification = PushNotificationDingV2 | PushNotificationAlarmV2;
947
942
  export interface SocketTicketResponse {
948
943
  ticket: string;
949
944
  responseTimestampe: number;
package/lib/ring-types.js CHANGED
@@ -188,8 +188,11 @@ var PushNotificationAction;
188
188
  (function (PushNotificationAction) {
189
189
  PushNotificationAction["Ding"] = "com.ring.pn.live-event.ding";
190
190
  PushNotificationAction["Motion"] = "com.ring.pn.live-event.motion";
191
- PushNotificationAction["IntercomUnlock"] = "com.ring.pn.live-event.intercom";
192
- PushNotificationAction["LowBattery"] = "com.ring.push.LOW_BATTERY_ALERT";
191
+ PushNotificationAction["IntercomUnlock"] = "com.ring.pn.intercom.virtual.unlock";
192
+ PushNotificationAction["AlarmModeNone"] = "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_NONE_NOTICE";
193
+ PushNotificationAction["AlarmModeSome"] = "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_SOME_NOTICE";
194
+ PushNotificationAction["AlarmSoundSiren"] = "com.ring.push.HANDLE_NEW_USER_SOUND_SIREN";
195
+ PushNotificationAction["AlarmSilenceSiren"] = "com.ring.push.HANDLE_NEW_NON_ALARM_SIREN_SILENCED";
193
196
  })(PushNotificationAction || (exports.PushNotificationAction = PushNotificationAction = {}));
194
197
  // eslint-disable-next-line no-shadow
195
198
  var DispatchSignalType;
package/lib/util.d.ts CHANGED
@@ -21,4 +21,5 @@ export type DeepPartial<T> = {
21
21
  };
22
22
  export declare function fromBase64(encodedInput: string): string;
23
23
  export declare function toBase64(input: string): string;
24
+ export declare function buildSearchString(search: Record<string, any>): string;
24
25
  export {};
package/lib/util.js CHANGED
@@ -19,6 +19,7 @@ exports.randomInteger = randomInteger;
19
19
  exports.randomString = randomString;
20
20
  exports.fromBase64 = fromBase64;
21
21
  exports.toBase64 = toBase64;
22
+ exports.buildSearchString = buildSearchString;
22
23
  const debug_1 = __importDefault(require("debug"));
23
24
  const colors_1 = require("colors");
24
25
  const readline_1 = require("readline");
@@ -143,3 +144,10 @@ function toBase64(input) {
143
144
  const buff = Buffer.from(input);
144
145
  return buff.toString('base64');
145
146
  }
147
+ function buildSearchString(search) {
148
+ return ('?' +
149
+ Object.entries(search)
150
+ .filter(([, value]) => value !== undefined && value !== null)
151
+ .map(([key, value]) => `${key}=${value}`)
152
+ .join('&'));
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ring-client-api",
3
- "version": "13.0.0-beta.0",
3
+ "version": "13.0.0-beta.2",
4
4
  "description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -19,19 +19,14 @@
19
19
  "dependencies": {
20
20
  "@eneris/push-receiver": "^4.1.2",
21
21
  "@homebridge/camera-utils": "^2.2.6",
22
- "@peculiar/asn1-schema": "^2.3.8",
23
22
  "@types/socket.io-client": "1.4.36",
24
23
  "colors": "1.4.0",
25
24
  "debug": "^4.3.5",
26
- "got": "^11.8.5",
27
25
  "json-bigint": "^1.0.0",
28
- "msw": "^1.3.3",
29
- "pvtsutils": "^1.3.5",
30
26
  "rxjs": "^7.8.1",
31
27
  "socket.io-client": "^2.5.0",
32
28
  "systeminformation": "^5.22.11",
33
29
  "uuid": "^10.0.0",
34
- "webcrypto-core": "^1.8.0",
35
30
  "werift": "0.19.3",
36
31
  "ws": "^8.17.1"
37
32
  },
@@ -44,6 +39,7 @@
44
39
  "@types/ws": "^8.5.10",
45
40
  "eslint-config-shared": "*",
46
41
  "jest": "29.7.0",
42
+ "msw": "^1.3.3",
47
43
  "ts-jest": "29.1.5",
48
44
  "tsconfig": "*",
49
45
  "typescript": "5.5.2"