ring-client-api 11.7.5 → 11.7.7
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/README.md +4 -0
- package/lib/api.js +10 -4
- package/lib/rest-client.d.ts +1 -0
- package/lib/rest-client.js +20 -1
- package/lib/ring-camera.js +5 -5
- package/lib/ring-types.d.ts +1 -1
- package/lib/test/rest-client.spec.d.ts +1 -0
- package/lib/test/rest-client.spec.js +237 -0
- package/lib/util.d.ts +1 -0
- package/lib/util.js +13 -2
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ the [Ring Alarm System](https://shop.ring.com/pages/security-system),
|
|
|
11
11
|
and third party devices that connect to the Ring Alarm System.
|
|
12
12
|
Built to support the [homebridge-ring Plugin](../homebridge-ring)
|
|
13
13
|
|
|
14
|
+
## Troubleshooting Issues
|
|
15
|
+
|
|
16
|
+
If you are having issues, please look for related articles in the [wiki](https://github.com/dgreif/ring/wiki) and search existing [Issues](https://github.com/dgreif/ring/issues) before opening a new Issue/Discussion
|
|
17
|
+
|
|
14
18
|
## Installation
|
|
15
19
|
|
|
16
20
|
`npm i ring-client-api`
|
package/lib/api.js
CHANGED
|
@@ -16,6 +16,7 @@ const ffmpeg_1 = require("./ffmpeg");
|
|
|
16
16
|
const subscribed_1 = require("./subscribed");
|
|
17
17
|
const push_receiver_1 = __importDefault(require("@eneris/push-receiver"));
|
|
18
18
|
const ring_intercom_1 = require("./ring-intercom");
|
|
19
|
+
const json_bigint_1 = __importDefault(require("json-bigint"));
|
|
19
20
|
class RingApi extends subscribed_1.Subscribed {
|
|
20
21
|
constructor(options) {
|
|
21
22
|
super();
|
|
@@ -127,14 +128,18 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
127
128
|
senderId: '876313859327', // for Ring android app. 703521446232 for ring-site
|
|
128
129
|
}), devicesById = {}, sendToDevice = (id, notification) => {
|
|
129
130
|
devicesById[id]?.processPushNotification(notification);
|
|
130
|
-
};
|
|
131
|
+
}, onPushNotificationToken = new rxjs_1.Subject();
|
|
131
132
|
for (const camera of cameras) {
|
|
132
133
|
devicesById[camera.id] = camera;
|
|
133
134
|
}
|
|
134
135
|
for (const intercom of intercoms) {
|
|
135
136
|
devicesById[intercom.id] = intercom;
|
|
136
137
|
}
|
|
137
|
-
pushReceiver.onCredentialsChanged(
|
|
138
|
+
pushReceiver.onCredentialsChanged(({ newCredentials: { fcm: { token }, }, }) => onPushNotificationToken.next(token));
|
|
139
|
+
this.addSubscriptions((0, rxjs_1.combineLatest)([
|
|
140
|
+
onPushNotificationToken,
|
|
141
|
+
this.restClient.onSession, // combined but not used here, just to trigger another request when session is updated
|
|
142
|
+
]).subscribe(async ([token]) => {
|
|
138
143
|
try {
|
|
139
144
|
await this.restClient.request({
|
|
140
145
|
url: (0, rest_client_1.clientApi)('device'),
|
|
@@ -154,11 +159,11 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
154
159
|
catch (e) {
|
|
155
160
|
(0, util_1.logError)(e);
|
|
156
161
|
}
|
|
157
|
-
});
|
|
162
|
+
}));
|
|
158
163
|
pushReceiver.onNotification(({ message }) => {
|
|
159
164
|
const dataJson = message.data?.gcmData;
|
|
160
165
|
try {
|
|
161
|
-
const notification =
|
|
166
|
+
const notification = (0, json_bigint_1.default)({ storeAsString: true }).parse(dataJson);
|
|
162
167
|
if ('ding' in notification) {
|
|
163
168
|
sendToDevice(notification.ding.doorbot_id, notification);
|
|
164
169
|
}
|
|
@@ -236,6 +241,7 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
236
241
|
(0, util_1.logError)(e);
|
|
237
242
|
});
|
|
238
243
|
this.restClient.clearTimeouts();
|
|
244
|
+
(0, util_1.clearTimeouts)();
|
|
239
245
|
}
|
|
240
246
|
}
|
|
241
247
|
exports.RingApi = RingApi;
|
package/lib/rest-client.d.ts
CHANGED
package/lib/rest-client.js
CHANGED
|
@@ -94,6 +94,7 @@ class RingRestClient {
|
|
|
94
94
|
this.sessionPromise = undefined;
|
|
95
95
|
this.using2fa = false;
|
|
96
96
|
this.onRefreshTokenUpdated = new rxjs_1.ReplaySubject(1);
|
|
97
|
+
this.onSession = new rxjs_1.ReplaySubject(1);
|
|
97
98
|
this.baseSessionMetadata = {
|
|
98
99
|
api_version: apiVersion,
|
|
99
100
|
device_model: this.authOptions.controlCenterDisplayName ?? 'ring-client-api',
|
|
@@ -161,6 +162,10 @@ class RingRestClient {
|
|
|
161
162
|
responseError.startsWith('Verification Code')) // invalid 2fa code entered
|
|
162
163
|
) {
|
|
163
164
|
this.using2fa = true;
|
|
165
|
+
if (response.statusCode === 400) {
|
|
166
|
+
this.promptFor2fa = 'Invalid 2fa code entered. Please try again.';
|
|
167
|
+
throw new Error(responseError);
|
|
168
|
+
}
|
|
164
169
|
if ('tsv_state' in responseData) {
|
|
165
170
|
const { tsv_state, phone } = responseData, prompt = tsv_state === 'totp'
|
|
166
171
|
? 'from your authenticator app'
|
|
@@ -205,7 +210,9 @@ class RingRestClient {
|
|
|
205
210
|
getSession() {
|
|
206
211
|
return this.authPromise.then(async (authToken) => {
|
|
207
212
|
try {
|
|
208
|
-
|
|
213
|
+
const session = await this.fetchNewSession(authToken);
|
|
214
|
+
this.onSession.next(session);
|
|
215
|
+
return session;
|
|
209
216
|
}
|
|
210
217
|
catch (e) {
|
|
211
218
|
const response = e.response || {};
|
|
@@ -232,8 +239,20 @@ class RingRestClient {
|
|
|
232
239
|
}
|
|
233
240
|
refreshSession() {
|
|
234
241
|
this.sessionPromise = this.getSession();
|
|
242
|
+
this.sessionPromise.finally(() => {
|
|
243
|
+
// Refresh the session every 12 hours
|
|
244
|
+
// This is needed to keep the session alive for users outside the US, due to Data Residency laws
|
|
245
|
+
// We believe Ring is clearing the session info after ~24 hours, which breaks Push Notifications
|
|
246
|
+
const timeout = setTimeout(() => {
|
|
247
|
+
this.refreshSession();
|
|
248
|
+
}, 12 * 60 * 60 * 1000); // 12 hours
|
|
249
|
+
this.timeouts.push(timeout);
|
|
250
|
+
});
|
|
235
251
|
}
|
|
236
252
|
async request(options) {
|
|
253
|
+
if (!this.sessionPromise) {
|
|
254
|
+
this.refreshSession();
|
|
255
|
+
}
|
|
237
256
|
const hardwareId = await this.hardwareIdPromise, url = options.url, initialSessionPromise = this.sessionPromise;
|
|
238
257
|
try {
|
|
239
258
|
await initialSessionPromise;
|
package/lib/ring-camera.js
CHANGED
|
@@ -99,18 +99,18 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
99
99
|
this.onInHomeDoorbellStatus = this.onData.pipe((0, operators_1.map)(({ settings: { chime_settings } }) => {
|
|
100
100
|
return Boolean(chime_settings?.enable);
|
|
101
101
|
}), (0, operators_1.distinctUntilChanged)());
|
|
102
|
-
|
|
102
|
+
this.addSubscriptions(this.restClient.onSession.subscribe(() => {
|
|
103
103
|
this.subscribeToDingEvents().catch((e) => {
|
|
104
104
|
(0, util_1.logError)('Failed to subscribe ' + initialData.description + ' to ding events');
|
|
105
105
|
(0, util_1.logError)(e);
|
|
106
106
|
});
|
|
107
|
-
}
|
|
108
|
-
if (!initialData.subscribed_motions) {
|
|
109
107
|
this.subscribeToMotionEvents().catch((e) => {
|
|
110
|
-
(0, util_1.logError)('Failed to subscribe ' +
|
|
108
|
+
(0, util_1.logError)('Failed to subscribe ' +
|
|
109
|
+
initialData.description +
|
|
110
|
+
' to motion events');
|
|
111
111
|
(0, util_1.logError)(e);
|
|
112
112
|
});
|
|
113
|
-
}
|
|
113
|
+
}));
|
|
114
114
|
}
|
|
115
115
|
updateData(update) {
|
|
116
116
|
this.onData.next(update);
|
package/lib/ring-types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,237 @@
|
|
|
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.rest.post('https://oauth.ring.com/oauth/token', async (req, res, ctx) => {
|
|
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 res(ctx.status(400), ctx.json({
|
|
15
|
+
code: 1,
|
|
16
|
+
error: 'Invalid auth headers: ' +
|
|
17
|
+
JSON.stringify(req.headers.raw(), null, 2),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
if (body.grant_type === 'refresh_token') {
|
|
21
|
+
if (body.refresh_token === refreshToken) {
|
|
22
|
+
// Valid refresh token
|
|
23
|
+
return res(ctx.status(200), ctx.json({
|
|
24
|
+
access_token: accessToken,
|
|
25
|
+
expires_in: 3600,
|
|
26
|
+
refresh_token: secondRefreshToken,
|
|
27
|
+
scope: 'client',
|
|
28
|
+
token_type: 'Bearer',
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
if (body.refresh_token === secondRefreshToken) {
|
|
32
|
+
// Valid refresh token
|
|
33
|
+
return res(ctx.status(200), ctx.json({
|
|
34
|
+
access_token: secondAccessToken,
|
|
35
|
+
expires_in: 3600,
|
|
36
|
+
refresh_token: thirdRefreshToken,
|
|
37
|
+
scope: 'client',
|
|
38
|
+
token_type: 'Bearer',
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
// Invalid refresh token
|
|
42
|
+
return res(ctx.status(401), ctx.json({
|
|
43
|
+
error: 'invalid_grant',
|
|
44
|
+
error_description: 'token is invalid or does not exists',
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
if (body.grant_type !== 'password' ||
|
|
48
|
+
body.client_id !== 'ring_official_android' ||
|
|
49
|
+
body.scope !== 'client') {
|
|
50
|
+
return res(ctx.status(400), ctx.json({
|
|
51
|
+
code: 1,
|
|
52
|
+
error: 'Invalid auth request: ' + JSON.stringify(body),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
if (body.username !== email || body.password !== password) {
|
|
56
|
+
// Wrong username or password
|
|
57
|
+
return res(ctx.status(401), ctx.json({
|
|
58
|
+
error: 'access_denied',
|
|
59
|
+
error_description: 'invalid user credentials',
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
if (req.headers.get('2fa-code') &&
|
|
63
|
+
req.headers.get('2fa-code') !== twoFactorAuthCode) {
|
|
64
|
+
// Wrong 2fa code
|
|
65
|
+
return res(ctx.status(400), ctx.json({
|
|
66
|
+
err_msg: 'bad request response from dependency service',
|
|
67
|
+
error: 'Verification Code is invalid or expired',
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
if (req.headers.get('2fa-code') === twoFactorAuthCode) {
|
|
71
|
+
// Successfull login with correct 2fa code
|
|
72
|
+
return res(ctx.json({
|
|
73
|
+
access_token: accessToken,
|
|
74
|
+
expires_in: 3600,
|
|
75
|
+
refresh_token: refreshToken,
|
|
76
|
+
scope: 'client',
|
|
77
|
+
token_type: 'Bearer',
|
|
78
|
+
responseTimestamp: Date.now(),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
// 2fa code not provided, so return the 2fa prompt
|
|
82
|
+
return res(ctx.status(412), ctx.json({
|
|
83
|
+
next_time_in_secs: 60,
|
|
84
|
+
phone,
|
|
85
|
+
tsv_state: 'sms',
|
|
86
|
+
}));
|
|
87
|
+
}), msw_1.rest.post('https://api.ring.com/clients_api/session', async (req, res, ctx) => {
|
|
88
|
+
const authHeader = req.headers.get('Authorization');
|
|
89
|
+
if (authHeader !== `Bearer ${accessToken}` &&
|
|
90
|
+
authHeader !== `Bearer ${secondAccessToken}`) {
|
|
91
|
+
// Invalid access token used
|
|
92
|
+
return res(ctx.status(401));
|
|
93
|
+
}
|
|
94
|
+
const body = await req.json();
|
|
95
|
+
if (body.device.hardware_id !== (await (0, util_1.getHardwareId)()) ||
|
|
96
|
+
body.device.metadata.api_version !== 11 ||
|
|
97
|
+
body.device.metadata.device_model !== 'ring-client-api' ||
|
|
98
|
+
body.device.os !== 'android') {
|
|
99
|
+
return res(ctx.status(400), ctx.body('Bad session request: ' + JSON.stringify(body, null, 2)));
|
|
100
|
+
}
|
|
101
|
+
// Fake a response from the session endpoint, incrementing the sessionCreatedCount
|
|
102
|
+
sessionCreatedCount++;
|
|
103
|
+
return res(ctx.json({
|
|
104
|
+
profile: {
|
|
105
|
+
id: 1234,
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
}));
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
sessionCreatedCount = 0;
|
|
111
|
+
});
|
|
112
|
+
beforeAll(() => {
|
|
113
|
+
// Establish requests interception layer before all tests.
|
|
114
|
+
server.listen();
|
|
115
|
+
});
|
|
116
|
+
afterAll(() => {
|
|
117
|
+
// Clean up after all tests are done, preventing this
|
|
118
|
+
// interception layer from affecting irrelevant tests.
|
|
119
|
+
server.close();
|
|
120
|
+
});
|
|
121
|
+
afterEach(() => {
|
|
122
|
+
client.clearTimeouts();
|
|
123
|
+
(0, util_1.clearTimeouts)();
|
|
124
|
+
});
|
|
125
|
+
describe('getAuth', () => {
|
|
126
|
+
test('It should throw and set the 2fa prompt', async () => {
|
|
127
|
+
client = new rest_client_1.RingRestClient({
|
|
128
|
+
password,
|
|
129
|
+
email,
|
|
130
|
+
});
|
|
131
|
+
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.');
|
|
132
|
+
expect(client.promptFor2fa).toEqual(`Please enter the code sent to ${phone} via sms`);
|
|
133
|
+
expect(client.using2fa).toEqual(true);
|
|
134
|
+
});
|
|
135
|
+
test('It should accept a 2fa code', async () => {
|
|
136
|
+
client = new rest_client_1.RingRestClient({
|
|
137
|
+
password,
|
|
138
|
+
email,
|
|
139
|
+
});
|
|
140
|
+
// ignore the first reject, it's tested above
|
|
141
|
+
await expect(() => client.getAuth()).rejects.toThrow();
|
|
142
|
+
// call getAuth again with the 2fa code, which should succeed
|
|
143
|
+
const auth = await client.getAuth(twoFactorAuthCode);
|
|
144
|
+
expect(auth).toMatchObject({
|
|
145
|
+
access_token: accessToken,
|
|
146
|
+
refresh_token: refreshToken,
|
|
147
|
+
});
|
|
148
|
+
expect(client.refreshToken).toEqual(refreshToken);
|
|
149
|
+
});
|
|
150
|
+
test('it should handle invalid credentials', async () => {
|
|
151
|
+
client = new rest_client_1.RingRestClient({
|
|
152
|
+
password: 'incorrect password',
|
|
153
|
+
email,
|
|
154
|
+
});
|
|
155
|
+
await expect(() => client.getAuth()).rejects.toThrow('Failed to fetch oauth token from Ring. Verify that your email and password are correct. (error: access_denied)');
|
|
156
|
+
});
|
|
157
|
+
test('it should handle invalid 2fa code', async () => {
|
|
158
|
+
client = new rest_client_1.RingRestClient({
|
|
159
|
+
password,
|
|
160
|
+
email,
|
|
161
|
+
});
|
|
162
|
+
// ignore the first reject, it's tested above
|
|
163
|
+
await expect(() => client.getAuth()).rejects.toThrow();
|
|
164
|
+
// call getAuth again with an invalid 2fa code, which should fail
|
|
165
|
+
await expect(() => client.getAuth('invalid 2fa code')).rejects.toThrow('Verification Code is invalid or expired');
|
|
166
|
+
expect(client.promptFor2fa).toEqual('Invalid 2fa code entered. Please try again.');
|
|
167
|
+
});
|
|
168
|
+
test('it should establish a valid auth token with a valid refresh token', async () => {
|
|
169
|
+
client = new rest_client_1.RingRestClient({
|
|
170
|
+
refreshToken,
|
|
171
|
+
});
|
|
172
|
+
expect(await client.getCurrentAuth()).toMatchObject({
|
|
173
|
+
access_token: accessToken,
|
|
174
|
+
refresh_token: secondRefreshToken,
|
|
175
|
+
});
|
|
176
|
+
expect(client.refreshToken).toEqual(secondRefreshToken);
|
|
177
|
+
});
|
|
178
|
+
test('it should emit an event when a new refresh token is created', async () => {
|
|
179
|
+
client = new rest_client_1.RingRestClient({
|
|
180
|
+
refreshToken,
|
|
181
|
+
});
|
|
182
|
+
const refreshedPromise = (0, rxjs_1.firstValueFrom)(client.onRefreshTokenUpdated), auth = await client.getAuth();
|
|
183
|
+
expect(auth).toMatchObject({
|
|
184
|
+
access_token: accessToken,
|
|
185
|
+
refresh_token: secondRefreshToken,
|
|
186
|
+
});
|
|
187
|
+
expect(await refreshedPromise).toEqual({
|
|
188
|
+
oldRefreshToken: refreshToken,
|
|
189
|
+
newRefreshToken: secondRefreshToken,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('fetch', () => {
|
|
194
|
+
let invalidateFirstAccessToken = false;
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
invalidateFirstAccessToken = false;
|
|
197
|
+
server.use(msw_1.rest.get('https://api.ring.com/devices/v1/locations', (req, res, ctx) => {
|
|
198
|
+
const authHeader = req.headers.get('Authorization');
|
|
199
|
+
if (invalidateFirstAccessToken &&
|
|
200
|
+
authHeader === `Bearer ${accessToken}`) {
|
|
201
|
+
// Original access token used, but no longer valid
|
|
202
|
+
return res(ctx.status(401));
|
|
203
|
+
}
|
|
204
|
+
if (authHeader !== `Bearer ${accessToken}` &&
|
|
205
|
+
authHeader !== `Bearer ${secondAccessToken}`) {
|
|
206
|
+
// Invalid access token used
|
|
207
|
+
return res(ctx.status(401));
|
|
208
|
+
}
|
|
209
|
+
if (sessionCreatedCount === 0) {
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.error('Request received before session was created');
|
|
212
|
+
// Session not created yet
|
|
213
|
+
return res(ctx.status(404), ctx.text('Session not created yet'));
|
|
214
|
+
}
|
|
215
|
+
return res(ctx.json([]));
|
|
216
|
+
}));
|
|
217
|
+
});
|
|
218
|
+
it('should include the auth token as a header', async () => {
|
|
219
|
+
client = new rest_client_1.RingRestClient({
|
|
220
|
+
refreshToken,
|
|
221
|
+
});
|
|
222
|
+
const response = await client.request({
|
|
223
|
+
url: 'https://api.ring.com/devices/v1/locations',
|
|
224
|
+
});
|
|
225
|
+
expect(response).toEqual([]);
|
|
226
|
+
});
|
|
227
|
+
it('should fetch a new auth token if the first is no longer valid', async () => {
|
|
228
|
+
client = new rest_client_1.RingRestClient({
|
|
229
|
+
refreshToken,
|
|
230
|
+
});
|
|
231
|
+
invalidateFirstAccessToken = true;
|
|
232
|
+
const response = await client.request({
|
|
233
|
+
url: 'https://api.ring.com/devices/v1/locations',
|
|
234
|
+
});
|
|
235
|
+
expect(response).toEqual([]);
|
|
236
|
+
});
|
|
237
|
+
});
|
package/lib/util.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ interface Logger {
|
|
|
2
2
|
logInfo: (...message: string[]) => void;
|
|
3
3
|
logError: (message: string) => void;
|
|
4
4
|
}
|
|
5
|
+
export declare function clearTimeouts(): void;
|
|
5
6
|
export declare function delay(milliseconds: number): Promise<unknown>;
|
|
6
7
|
export declare function logDebug(message: any): void;
|
|
7
8
|
export declare function logInfo(...message: any[]): void;
|
package/lib/util.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.randomString = exports.randomInteger = exports.mapAsync = exports.stringify = exports.requestInput = exports.getHardwareId = exports.generateUuid = exports.enableDebug = exports.useLogger = exports.logError = exports.logInfo = exports.logDebug = exports.delay = void 0;
|
|
6
|
+
exports.randomString = exports.randomInteger = exports.mapAsync = exports.stringify = exports.requestInput = exports.getHardwareId = exports.generateUuid = exports.enableDebug = exports.useLogger = exports.logError = exports.logInfo = exports.logDebug = exports.delay = exports.clearTimeouts = void 0;
|
|
7
7
|
const debug_1 = __importDefault(require("debug"));
|
|
8
8
|
const colors_1 = require("colors");
|
|
9
9
|
const readline_1 = require("readline");
|
|
@@ -19,9 +19,20 @@ let logger = {
|
|
|
19
19
|
debugLogger((0, colors_1.red)(message));
|
|
20
20
|
},
|
|
21
21
|
}, debugEnabled = false;
|
|
22
|
+
const timeouts = new Set();
|
|
23
|
+
function clearTimeouts() {
|
|
24
|
+
timeouts.forEach((timeout) => {
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
exports.clearTimeouts = clearTimeouts;
|
|
22
29
|
function delay(milliseconds) {
|
|
23
30
|
return new Promise((resolve) => {
|
|
24
|
-
setTimeout(
|
|
31
|
+
const timeout = setTimeout(() => {
|
|
32
|
+
timeouts.delete(timeout);
|
|
33
|
+
resolve(undefined);
|
|
34
|
+
}, milliseconds);
|
|
35
|
+
timeouts.add(timeout);
|
|
25
36
|
});
|
|
26
37
|
}
|
|
27
38
|
exports.delay = delay;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ring-client-api",
|
|
3
|
-
"version": "11.7.
|
|
3
|
+
"version": "11.7.7",
|
|
4
4
|
"description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"colors": "1.4.0",
|
|
26
26
|
"debug": "^4.3.4",
|
|
27
27
|
"got": "^11.8.5",
|
|
28
|
+
"json-bigint": "^1.0.0",
|
|
29
|
+
"msw": "^1.2.1",
|
|
28
30
|
"rxjs": "^7.8.0",
|
|
29
31
|
"socket.io-client": "^2.5.0",
|
|
30
32
|
"systeminformation": "^5.17.12",
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
"devDependencies": {
|
|
37
39
|
"@types/debug": "4.1.7",
|
|
38
40
|
"@types/jest": "29.5.0",
|
|
41
|
+
"@types/json-bigint": "^1.0.1",
|
|
39
42
|
"@types/node": "18.15.9",
|
|
40
43
|
"@types/uuid": "9.0.1",
|
|
41
44
|
"@types/ws": "^8.5.4",
|