ring-client-api 13.2.1 → 14.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/lib/api.d.ts +9 -7
- package/lib/api.js +56 -63
- package/lib/device-data.js +6 -9
- package/lib/ffmpeg.js +2 -6
- package/lib/index.d.ts +7 -7
- package/lib/index.js +7 -23
- package/lib/location.d.ts +22 -21
- package/lib/location.js +61 -65
- package/lib/refresh-token.js +6 -10
- package/lib/rest-client.d.ts +3 -3
- package/lib/rest-client.js +39 -50
- package/lib/ring-auth-cli.js +2 -4
- package/lib/ring-camera.d.ts +21 -18
- package/lib/ring-camera.js +57 -56
- package/lib/ring-chime.d.ts +5 -5
- package/lib/ring-chime.js +10 -14
- package/lib/ring-device-data-cli.js +2 -4
- package/lib/ring-device.d.ts +4 -4
- package/lib/ring-device.js +12 -16
- package/lib/ring-intercom.d.ts +6 -6
- package/lib/ring-intercom.js +19 -23
- package/lib/ring-types.d.ts +2 -0
- package/lib/ring-types.js +24 -24
- package/lib/streaming/peer-connection.d.ts +5 -3
- package/lib/streaming/peer-connection.js +25 -30
- package/lib/streaming/simple-webrtc-session.d.ts +2 -2
- package/lib/streaming/simple-webrtc-session.js +3 -7
- package/lib/streaming/streaming-messages.js +1 -2
- package/lib/streaming/streaming-session.d.ts +3 -3
- package/lib/streaming/streaming-session.js +34 -38
- package/lib/streaming/webrtc-connection.d.ts +4 -4
- package/lib/streaming/webrtc-connection.js +38 -42
- package/lib/subscribed.d.ts +1 -1
- package/lib/subscribed.js +1 -5
- package/lib/util.js +33 -54
- package/package.json +14 -16
- package/lib/test/rest-client.spec.d.ts +0 -1
- package/lib/test/rest-client.spec.js +0 -242
- package/lib/test/ring-camera.spec.d.ts +0 -1
- package/lib/test/ring-camera.spec.js +0 -37
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const msw_1 = require("msw");
|
|
4
|
-
const node_1 = require("msw/node");
|
|
5
|
-
const rest_client_1 = require("../rest-client");
|
|
6
|
-
const util_1 = require("../util");
|
|
7
|
-
const rxjs_1 = require("rxjs");
|
|
8
|
-
let sessionCreatedCount = 0, client;
|
|
9
|
-
const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoFactorAuthCode = '123456', hardwareIdPromise = (0, util_1.getHardwareId)(), accessToken = 'ey__accees_token', secondAccessToken = 'ey__second_accees_token', refreshToken = 'ey__refresh_token', secondRefreshToken = 'ey__second_refresh_token', thirdRefreshToken = 'ey__third_refresh_token', server = (0, node_1.setupServer)(msw_1.http.post('https://oauth.ring.com/oauth/token', async ({ request: req }) => {
|
|
10
|
-
const body = await req.json();
|
|
11
|
-
if (req.headers.get('2fa-support') !== 'true' ||
|
|
12
|
-
req.headers.get('User-Agent') !== 'android:com.ringapp' ||
|
|
13
|
-
req.headers.get('hardware_id') !== (await hardwareIdPromise)) {
|
|
14
|
-
return msw_1.HttpResponse.json({
|
|
15
|
-
code: 1,
|
|
16
|
-
error: 'Invalid auth headers: ' + JSON.stringify(req.headers, null, 2),
|
|
17
|
-
}, { status: 400 });
|
|
18
|
-
}
|
|
19
|
-
if (body.grant_type === 'refresh_token') {
|
|
20
|
-
if (body.refresh_token === refreshToken) {
|
|
21
|
-
// Valid refresh token
|
|
22
|
-
return msw_1.HttpResponse.json({
|
|
23
|
-
access_token: accessToken,
|
|
24
|
-
expires_in: 3600,
|
|
25
|
-
refresh_token: secondRefreshToken,
|
|
26
|
-
scope: 'client',
|
|
27
|
-
token_type: 'Bearer',
|
|
28
|
-
}, { status: 200 });
|
|
29
|
-
}
|
|
30
|
-
if (body.refresh_token === secondRefreshToken) {
|
|
31
|
-
// Valid refresh token
|
|
32
|
-
return msw_1.HttpResponse.json({
|
|
33
|
-
access_token: secondAccessToken,
|
|
34
|
-
expires_in: 3600,
|
|
35
|
-
refresh_token: thirdRefreshToken,
|
|
36
|
-
scope: 'client',
|
|
37
|
-
token_type: 'Bearer',
|
|
38
|
-
}, { status: 200 });
|
|
39
|
-
}
|
|
40
|
-
// Invalid refresh token
|
|
41
|
-
return msw_1.HttpResponse.json({
|
|
42
|
-
error: 'invalid_grant',
|
|
43
|
-
error_description: 'token is invalid or does not exists',
|
|
44
|
-
}, { status: 401 });
|
|
45
|
-
}
|
|
46
|
-
if (body.grant_type !== 'password' ||
|
|
47
|
-
body.client_id !== 'ring_official_android' ||
|
|
48
|
-
body.scope !== 'client') {
|
|
49
|
-
return msw_1.HttpResponse.json({
|
|
50
|
-
code: 1,
|
|
51
|
-
error: 'Invalid auth request: ' + JSON.stringify(body),
|
|
52
|
-
}, { status: 400 });
|
|
53
|
-
}
|
|
54
|
-
if (body.username !== email || body.password !== password) {
|
|
55
|
-
// Wrong username or password
|
|
56
|
-
return msw_1.HttpResponse.json({
|
|
57
|
-
error: 'access_denied',
|
|
58
|
-
error_description: 'invalid user credentials',
|
|
59
|
-
}, { status: 401 });
|
|
60
|
-
}
|
|
61
|
-
if (req.headers.get('2fa-code') &&
|
|
62
|
-
req.headers.get('2fa-code') !== twoFactorAuthCode) {
|
|
63
|
-
// Wrong 2fa code
|
|
64
|
-
return msw_1.HttpResponse.json({
|
|
65
|
-
err_msg: 'bad request response from dependency service',
|
|
66
|
-
error: 'Verification Code is invalid or expired',
|
|
67
|
-
}, { status: 400 });
|
|
68
|
-
}
|
|
69
|
-
if (req.headers.get('2fa-code') === twoFactorAuthCode) {
|
|
70
|
-
// Successfull login with correct 2fa code
|
|
71
|
-
return msw_1.HttpResponse.json({
|
|
72
|
-
access_token: accessToken,
|
|
73
|
-
expires_in: 3600,
|
|
74
|
-
refresh_token: refreshToken,
|
|
75
|
-
scope: 'client',
|
|
76
|
-
token_type: 'Bearer',
|
|
77
|
-
responseTimestamp: Date.now(),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
// 2fa code not provided, so return the 2fa prompt
|
|
81
|
-
return msw_1.HttpResponse.json({
|
|
82
|
-
next_time_in_secs: 60,
|
|
83
|
-
phone,
|
|
84
|
-
tsv_state: 'sms',
|
|
85
|
-
}, { status: 412 });
|
|
86
|
-
}), msw_1.http.post('https://api.ring.com/clients_api/session', async ({ request: req }) => {
|
|
87
|
-
const authHeader = req.headers.get('Authorization');
|
|
88
|
-
if (authHeader !== `Bearer ${accessToken}` &&
|
|
89
|
-
authHeader !== `Bearer ${secondAccessToken}`) {
|
|
90
|
-
// Invalid access token used
|
|
91
|
-
return msw_1.HttpResponse.json({}, { status: 401 });
|
|
92
|
-
}
|
|
93
|
-
const body = await req.json();
|
|
94
|
-
if (body.device.hardware_id !== (await (0, util_1.getHardwareId)()) ||
|
|
95
|
-
body.device.metadata.api_version !== 11 ||
|
|
96
|
-
body.device.metadata.device_model !== 'ring-client-api' ||
|
|
97
|
-
body.device.os !== 'android') {
|
|
98
|
-
return msw_1.HttpResponse.text('Bad session request: ' + JSON.stringify(body, null, 2), { status: 400 });
|
|
99
|
-
}
|
|
100
|
-
// Fake a response from the session endpoint, incrementing the sessionCreatedCount
|
|
101
|
-
sessionCreatedCount++;
|
|
102
|
-
return msw_1.HttpResponse.json({
|
|
103
|
-
profile: {
|
|
104
|
-
id: 1234,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
}));
|
|
108
|
-
async function wrapRefreshToken(rt) {
|
|
109
|
-
return (0, util_1.toBase64)(JSON.stringify({
|
|
110
|
-
rt,
|
|
111
|
-
hid: await hardwareIdPromise,
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
beforeEach(() => {
|
|
115
|
-
sessionCreatedCount = 0;
|
|
116
|
-
});
|
|
117
|
-
beforeAll(() => {
|
|
118
|
-
// Establish requests interception layer before all tests.
|
|
119
|
-
server.listen();
|
|
120
|
-
});
|
|
121
|
-
afterAll(() => {
|
|
122
|
-
// Clean up after all tests are done, preventing this
|
|
123
|
-
// interception layer from affecting irrelevant tests.
|
|
124
|
-
server.close();
|
|
125
|
-
});
|
|
126
|
-
afterEach(() => {
|
|
127
|
-
client.clearTimeouts();
|
|
128
|
-
(0, util_1.clearTimeouts)();
|
|
129
|
-
});
|
|
130
|
-
describe('getAuth', () => {
|
|
131
|
-
test('It should throw and set the 2fa prompt', async () => {
|
|
132
|
-
client = new rest_client_1.RingRestClient({
|
|
133
|
-
password,
|
|
134
|
-
email,
|
|
135
|
-
});
|
|
136
|
-
await expect(() => client.getAuth()).rejects.toThrow('Your Ring account is configured to use 2-factor authentication (2fa). See https://github.com/dgreif/ring/wiki/Refresh-Tokens for details.');
|
|
137
|
-
expect(client.promptFor2fa).toEqual(`Please enter the code sent to ${phone} via sms`);
|
|
138
|
-
expect(client.using2fa).toEqual(true);
|
|
139
|
-
});
|
|
140
|
-
test('It should accept a 2fa code', async () => {
|
|
141
|
-
client = new rest_client_1.RingRestClient({
|
|
142
|
-
password,
|
|
143
|
-
email,
|
|
144
|
-
});
|
|
145
|
-
// ignore the first reject, it's tested above
|
|
146
|
-
await expect(() => client.getAuth()).rejects.toThrow();
|
|
147
|
-
// call getAuth again with the 2fa code, which should succeed
|
|
148
|
-
const auth = await client.getAuth(twoFactorAuthCode);
|
|
149
|
-
expect(auth).toMatchObject({
|
|
150
|
-
access_token: accessToken,
|
|
151
|
-
refresh_token: await wrapRefreshToken(refreshToken),
|
|
152
|
-
});
|
|
153
|
-
expect(client.refreshToken).toEqual(await wrapRefreshToken(refreshToken));
|
|
154
|
-
});
|
|
155
|
-
test('it should handle invalid credentials', async () => {
|
|
156
|
-
client = new rest_client_1.RingRestClient({
|
|
157
|
-
password: 'incorrect password',
|
|
158
|
-
email,
|
|
159
|
-
});
|
|
160
|
-
await expect(() => client.getAuth()).rejects.toThrow('Failed to fetch oauth token from Ring. Verify that your email and password are correct. (error: access_denied)');
|
|
161
|
-
});
|
|
162
|
-
test('it should handle invalid 2fa code', async () => {
|
|
163
|
-
client = new rest_client_1.RingRestClient({
|
|
164
|
-
password,
|
|
165
|
-
email,
|
|
166
|
-
});
|
|
167
|
-
// ignore the first reject, it's tested above
|
|
168
|
-
await expect(() => client.getAuth()).rejects.toThrow();
|
|
169
|
-
// call getAuth again with an invalid 2fa code, which should fail
|
|
170
|
-
await expect(() => client.getAuth('invalid 2fa code')).rejects.toThrow('Verification Code is invalid or expired');
|
|
171
|
-
expect(client.promptFor2fa).toEqual('Invalid 2fa code entered. Please try again.');
|
|
172
|
-
});
|
|
173
|
-
test('it should establish a valid auth token with a valid refresh token', async () => {
|
|
174
|
-
client = new rest_client_1.RingRestClient({
|
|
175
|
-
refreshToken,
|
|
176
|
-
});
|
|
177
|
-
expect(await client.getCurrentAuth()).toMatchObject({
|
|
178
|
-
access_token: accessToken,
|
|
179
|
-
refresh_token: await wrapRefreshToken(secondRefreshToken),
|
|
180
|
-
});
|
|
181
|
-
expect(client.refreshToken).toEqual(await wrapRefreshToken(secondRefreshToken));
|
|
182
|
-
});
|
|
183
|
-
test('it should emit an event when a new refresh token is created', async () => {
|
|
184
|
-
client = new rest_client_1.RingRestClient({
|
|
185
|
-
refreshToken,
|
|
186
|
-
});
|
|
187
|
-
const refreshedPromise = (0, rxjs_1.firstValueFrom)(client.onRefreshTokenUpdated), auth = await client.getAuth();
|
|
188
|
-
expect(auth).toMatchObject({
|
|
189
|
-
access_token: accessToken,
|
|
190
|
-
refresh_token: await wrapRefreshToken(secondRefreshToken),
|
|
191
|
-
});
|
|
192
|
-
expect(await refreshedPromise).toEqual({
|
|
193
|
-
oldRefreshToken: refreshToken,
|
|
194
|
-
newRefreshToken: await wrapRefreshToken(secondRefreshToken),
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
describe('fetch', () => {
|
|
199
|
-
let invalidateFirstAccessToken = false;
|
|
200
|
-
beforeEach(() => {
|
|
201
|
-
invalidateFirstAccessToken = false;
|
|
202
|
-
server.use(msw_1.http.get('https://api.ring.com/clients_api/some_endpoint', ({ request: req }) => {
|
|
203
|
-
const authHeader = req.headers.get('Authorization');
|
|
204
|
-
if (invalidateFirstAccessToken &&
|
|
205
|
-
authHeader === `Bearer ${accessToken}`) {
|
|
206
|
-
// Original access token used, but no longer valid
|
|
207
|
-
return msw_1.HttpResponse.json({}, { status: 401 });
|
|
208
|
-
}
|
|
209
|
-
if (authHeader !== `Bearer ${accessToken}` &&
|
|
210
|
-
authHeader !== `Bearer ${secondAccessToken}`) {
|
|
211
|
-
// Invalid access token used
|
|
212
|
-
return msw_1.HttpResponse.json({}, { status: 401 });
|
|
213
|
-
}
|
|
214
|
-
if (sessionCreatedCount === 0) {
|
|
215
|
-
// Session not created yet
|
|
216
|
-
return msw_1.HttpResponse.json({
|
|
217
|
-
error: 'Session not found for ' + req.headers.get('hardware_id'),
|
|
218
|
-
}, { status: 404 });
|
|
219
|
-
}
|
|
220
|
-
return msw_1.HttpResponse.json([]);
|
|
221
|
-
}));
|
|
222
|
-
});
|
|
223
|
-
it('should include the auth token as a header', async () => {
|
|
224
|
-
client = new rest_client_1.RingRestClient({
|
|
225
|
-
refreshToken,
|
|
226
|
-
});
|
|
227
|
-
const response = await client.request({
|
|
228
|
-
url: 'https://api.ring.com/clients_api/some_endpoint',
|
|
229
|
-
});
|
|
230
|
-
expect(response).toEqual([]);
|
|
231
|
-
});
|
|
232
|
-
it('should fetch a new auth token if the first is no longer valid', async () => {
|
|
233
|
-
client = new rest_client_1.RingRestClient({
|
|
234
|
-
refreshToken,
|
|
235
|
-
});
|
|
236
|
-
invalidateFirstAccessToken = true;
|
|
237
|
-
const response = await client.request({
|
|
238
|
-
url: 'https://api.ring.com/clients_api/some_endpoint',
|
|
239
|
-
});
|
|
240
|
-
expect(response).toEqual([]);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const index_1 = require("../index");
|
|
4
|
-
describe('Ring Camera', () => {
|
|
5
|
-
describe('battery level', () => {
|
|
6
|
-
it('should handle string battery life', () => {
|
|
7
|
-
expect((0, index_1.getBatteryLevel)({
|
|
8
|
-
battery_life: '49',
|
|
9
|
-
})).toEqual(49);
|
|
10
|
-
});
|
|
11
|
-
it('should handle null battery life', () => {
|
|
12
|
-
expect((0, index_1.getBatteryLevel)({ battery_life: null })).toEqual(null);
|
|
13
|
-
});
|
|
14
|
-
it('should handle right battery only', () => {
|
|
15
|
-
expect((0, index_1.getBatteryLevel)({ battery_life: null, battery_life_2: 24 })).toEqual(24);
|
|
16
|
-
});
|
|
17
|
-
it('should handle left battery only', () => {
|
|
18
|
-
expect((0, index_1.getBatteryLevel)({ battery_life: 76, battery_life_2: null })).toEqual(76);
|
|
19
|
-
});
|
|
20
|
-
it('should handle dual batteries', () => {
|
|
21
|
-
expect((0, index_1.getBatteryLevel)({ battery_life: '92', battery_life_2: 84 })).toEqual(84);
|
|
22
|
-
expect((0, index_1.getBatteryLevel)({ battery_life: '92', battery_life_2: 100 })).toEqual(92);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
describe('cleanSnapshotUuid', () => {
|
|
26
|
-
it('should return the original uuid if it is already clean', () => {
|
|
27
|
-
expect((0, index_1.cleanSnapshotUuid)('c2a0a397-3538-422d-bb4e-51837a56b870')).toBe('c2a0a397-3538-422d-bb4e-51837a56b870');
|
|
28
|
-
});
|
|
29
|
-
it('should return remove anything after :', () => {
|
|
30
|
-
expect((0, index_1.cleanSnapshotUuid)('c2a0a397-3538-422d-bb4e-51837a56b870:122429140')).toBe('c2a0a397-3538-422d-bb4e-51837a56b870');
|
|
31
|
-
});
|
|
32
|
-
it('should handle falsy values', () => {
|
|
33
|
-
expect((0, index_1.cleanSnapshotUuid)(undefined)).toBe(undefined);
|
|
34
|
-
expect((0, index_1.cleanSnapshotUuid)(null)).toBe(null);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
});
|