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 +8 -1
- package/lib/rest-client.d.ts +6 -1
- package/lib/rest-client.js +75 -19
- package/lib/ring-camera.d.ts +2 -2
- package/lib/ring-camera.js +3 -4
- package/lib/ring-intercom.d.ts +2 -2
- package/lib/ring-types.d.ts +8 -13
- package/lib/ring-types.js +5 -2
- package/lib/util.d.ts +1 -0
- package/lib/util.js +8 -0
- package/package.json +2 -6
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
|
|
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
|
}
|
package/lib/rest-client.d.ts
CHANGED
|
@@ -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 {};
|
package/lib/rest-client.js
CHANGED
|
@@ -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
|
-
}
|
|
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
|
-
|
|
47
|
-
|
|
100
|
+
const date = headers.get('date');
|
|
101
|
+
if (date) {
|
|
102
|
+
data.responseTimestamp = new Date(date).getTime();
|
|
48
103
|
}
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
190
|
-
(response.
|
|
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.
|
|
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.
|
|
304
|
+
if (response.status === 401) {
|
|
249
305
|
await this.refreshAuth();
|
|
250
306
|
return this.getSession();
|
|
251
307
|
}
|
|
252
|
-
if (response.
|
|
253
|
-
const retryAfter = e.response.headers
|
|
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.
|
|
356
|
+
if (response.status === 401) {
|
|
301
357
|
await this.refreshAuth();
|
|
302
358
|
return this.request(options);
|
|
303
359
|
}
|
|
304
|
-
if (response.
|
|
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.
|
|
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.
|
|
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.
|
|
335
|
-
(0, util_1.logError)(`Request to ${url} failed with status ${response.
|
|
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:`);
|
package/lib/ring-camera.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CameraData, CameraDeviceSettingsData, CameraEventOptions, CameraEventResponse, CameraHealth, HistoryOptions, PeriodicFootageResponse,
|
|
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:
|
|
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;
|
package/lib/ring-camera.js
CHANGED
|
@@ -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
|
},
|
package/lib/ring-intercom.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IntercomHandsetAudioData,
|
|
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:
|
|
25
|
+
processPushNotification(notification: PushNotificationDingV2): void;
|
|
26
26
|
}
|
package/lib/ring-types.d.ts
CHANGED
|
@@ -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.
|
|
871
|
-
|
|
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
|
|
936
|
+
export interface PushNotificationAlarmV2 {
|
|
934
937
|
data: {
|
|
935
|
-
|
|
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.
|
|
192
|
-
PushNotificationAction["
|
|
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
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.
|
|
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"
|