ring-client-api 10.0.0-beta.8 → 11.0.1

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.
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
4
  };
@@ -40,37 +31,37 @@ function appApi(path) {
40
31
  return appApiBaseUrl + path;
41
32
  }
42
33
  exports.appApi = appApi;
43
- function requestWithRetry(requestOptions) {
44
- return __awaiter(this, void 0, void 0, function* () {
45
- try {
46
- const options = Object.assign(Object.assign({}, defaultRequestOptions), requestOptions), { headers, body } = (yield (0, got_1.default)(options)), data = body;
47
- if (data !== null && typeof data === 'object') {
48
- if (headers.date) {
49
- data.responseTimestamp = new Date(headers.date).getTime();
50
- }
51
- if (headers['x-time-millis']) {
52
- data.timeMillis = Number(headers['x-time-millis']);
53
- }
34
+ async function requestWithRetry(requestOptions) {
35
+ try {
36
+ const options = {
37
+ ...defaultRequestOptions,
38
+ ...requestOptions,
39
+ }, { headers, body } = (await (0, got_1.default)(options)), data = body;
40
+ if (data !== null && typeof data === 'object') {
41
+ if (headers.date) {
42
+ data.responseTimestamp = new Date(headers.date).getTime();
43
+ }
44
+ if (headers['x-time-millis']) {
45
+ data.timeMillis = Number(headers['x-time-millis']);
54
46
  }
55
- return data;
56
47
  }
57
- catch (e) {
58
- if (!e.response && !requestOptions.allowNoResponse) {
59
- (0, util_1.logError)(`Failed to reach Ring server at ${requestOptions.url}. ${e.message}. Trying again in 5 seconds...`);
60
- if (e.message.includes('NGHTTP2_ENHANCE_YOUR_CALM')) {
61
- (0, util_1.logError)(`There is a known issue with your current NodeJS version (${process.version}). Please see https://github.com/dgreif/ring/wiki/NGHTTP2_ENHANCE_YOUR_CALM-Error for details`);
62
- }
63
- (0, util_1.logDebug)(e);
64
- yield (0, util_1.delay)(5000);
65
- return requestWithRetry(requestOptions);
48
+ return data;
49
+ }
50
+ catch (e) {
51
+ if (!e.response && !requestOptions.allowNoResponse) {
52
+ (0, util_1.logError)(`Failed to reach Ring server at ${requestOptions.url}. ${e.message}. Trying again in 5 seconds...`);
53
+ if (e.message.includes('NGHTTP2_ENHANCE_YOUR_CALM')) {
54
+ (0, util_1.logError)(`There is a known issue with your current NodeJS version (${process.version}). Please see https://github.com/dgreif/ring/wiki/NGHTTP2_ENHANCE_YOUR_CALM-Error for details`);
66
55
  }
67
- throw e;
56
+ (0, util_1.logDebug)(e);
57
+ await (0, util_1.delay)(5000);
58
+ return requestWithRetry(requestOptions);
68
59
  }
69
- });
60
+ throw e;
61
+ }
70
62
  }
71
63
  class RingRestClient {
72
64
  constructor(authOptions) {
73
- var _a;
74
65
  this.authOptions = authOptions;
75
66
  this.timeouts = [];
76
67
  this.sessionPromise = undefined;
@@ -78,7 +69,7 @@ class RingRestClient {
78
69
  this.onRefreshTokenUpdated = new rxjs_1.ReplaySubject(1);
79
70
  this.baseSessionMetadata = {
80
71
  api_version: apiVersion,
81
- device_model: (_a = this.authOptions.controlCenterDisplayName) !== null && _a !== void 0 ? _a : 'ring-client-api',
72
+ device_model: this.authOptions.controlCenterDisplayName ?? 'ring-client-api',
82
73
  };
83
74
  this.refreshToken =
84
75
  'refreshToken' in this.authOptions
@@ -126,95 +117,95 @@ class RingRestClient {
126
117
  }
127
118
  throw new Error('Refresh token is not valid. Unable to authenticate with Ring servers. See https://github.com/dgreif/ring/wiki/Refresh-Tokens');
128
119
  }
129
- getAuth(twoFactorAuthCode) {
130
- return __awaiter(this, void 0, void 0, function* () {
131
- const grantData = this.getGrantData(twoFactorAuthCode);
132
- try {
133
- const response = yield requestWithRetry({
134
- url: 'https://oauth.ring.com/oauth/token',
135
- json: Object.assign({ client_id: 'ring_official_android', scope: 'client' }, grantData),
136
- method: 'POST',
137
- headers: {
138
- '2fa-support': 'true',
139
- '2fa-code': twoFactorAuthCode || '',
140
- hardware_id: yield this.hardwareIdPromise,
141
- },
142
- });
143
- this.onRefreshTokenUpdated.next({
144
- oldRefreshToken: this.refreshToken,
145
- newRefreshToken: response.refresh_token,
146
- });
147
- this.refreshToken = response.refresh_token;
148
- return response;
149
- }
150
- catch (requestError) {
151
- if (grantData.refresh_token) {
152
- // failed request with refresh token
153
- this.refreshToken = undefined;
154
- (0, util_1.logError)(requestError);
155
- return this.getAuth();
156
- }
157
- const response = requestError.response || {}, responseData = response.body || {}, responseError = 'error' in responseData && typeof responseData.error === 'string'
158
- ? responseData.error
159
- : '';
160
- if (response.statusCode === 412 || // need 2fa code
161
- (response.statusCode === 400 &&
162
- responseError.startsWith('Verification Code')) // invalid 2fa code entered
163
- ) {
164
- this.using2fa = true;
165
- if ('tsv_state' in responseData) {
166
- const { tsv_state, phone } = responseData, prompt = tsv_state === 'totp'
167
- ? 'from your authenticator app'
168
- : `sent to ${phone} via ${tsv_state}`;
169
- this.promptFor2fa = `Please enter the code ${prompt}`;
170
- }
171
- else {
172
- this.promptFor2fa = 'Please enter the code sent to your text/email';
173
- }
174
- throw new Error('Your Ring account is configured to use 2-factor authentication (2fa). See https://github.com/dgreif/ring/wiki/Refresh-Tokens for details.');
175
- }
176
- const authTypeMessage = 'refreshToken' in this.authOptions
177
- ? 'refresh token is'
178
- : 'email and password are', errorMessage = 'Failed to fetch oauth token from Ring. ' +
179
- ('error_description' in responseData &&
180
- responseData.error_description ===
181
- 'too many requests from dependency service'
182
- ? 'You have requested too many 2fa codes. Ring limits 2fa to 10 codes within 10 minutes. Please try again in 10 minutes.'
183
- : `Verify that your ${authTypeMessage} correct.`) +
184
- ` (error: ${responseError})`;
185
- (0, util_1.logError)(requestError.response || requestError);
186
- (0, util_1.logError)(errorMessage);
187
- throw new Error(errorMessage);
188
- }
189
- });
190
- }
191
- fetchNewSession(authToken) {
192
- return __awaiter(this, void 0, void 0, function* () {
193
- return requestWithRetry({
194
- url: clientApi('session'),
120
+ async getAuth(twoFactorAuthCode) {
121
+ const grantData = this.getGrantData(twoFactorAuthCode);
122
+ try {
123
+ const response = await requestWithRetry({
124
+ url: 'https://oauth.ring.com/oauth/token',
195
125
  json: {
196
- device: {
197
- hardware_id: yield this.hardwareIdPromise,
198
- metadata: this.baseSessionMetadata,
199
- os: 'android', // can use android, ios, ring-site, windows for sure
200
- },
126
+ client_id: 'ring_official_android',
127
+ scope: 'client',
128
+ ...grantData,
201
129
  },
202
130
  method: 'POST',
203
131
  headers: {
204
- authorization: `Bearer ${authToken.access_token}`,
132
+ '2fa-support': 'true',
133
+ '2fa-code': twoFactorAuthCode || '',
134
+ hardware_id: await this.hardwareIdPromise,
205
135
  },
206
136
  });
137
+ this.onRefreshTokenUpdated.next({
138
+ oldRefreshToken: this.refreshToken,
139
+ newRefreshToken: response.refresh_token,
140
+ });
141
+ this.refreshToken = response.refresh_token;
142
+ return response;
143
+ }
144
+ catch (requestError) {
145
+ if (grantData.refresh_token) {
146
+ // failed request with refresh token
147
+ this.refreshToken = undefined;
148
+ (0, util_1.logError)(requestError);
149
+ return this.getAuth();
150
+ }
151
+ const response = requestError.response || {}, responseData = response.body || {}, responseError = 'error' in responseData && typeof responseData.error === 'string'
152
+ ? responseData.error
153
+ : '';
154
+ if (response.statusCode === 412 || // need 2fa code
155
+ (response.statusCode === 400 &&
156
+ responseError.startsWith('Verification Code')) // invalid 2fa code entered
157
+ ) {
158
+ this.using2fa = true;
159
+ if ('tsv_state' in responseData) {
160
+ const { tsv_state, phone } = responseData, prompt = tsv_state === 'totp'
161
+ ? 'from your authenticator app'
162
+ : `sent to ${phone} via ${tsv_state}`;
163
+ this.promptFor2fa = `Please enter the code ${prompt}`;
164
+ }
165
+ else {
166
+ this.promptFor2fa = 'Please enter the code sent to your text/email';
167
+ }
168
+ throw new Error('Your Ring account is configured to use 2-factor authentication (2fa). See https://github.com/dgreif/ring/wiki/Refresh-Tokens for details.');
169
+ }
170
+ const authTypeMessage = 'refreshToken' in this.authOptions
171
+ ? 'refresh token is'
172
+ : 'email and password are', errorMessage = 'Failed to fetch oauth token from Ring. ' +
173
+ ('error_description' in responseData &&
174
+ responseData.error_description ===
175
+ 'too many requests from dependency service'
176
+ ? 'You have requested too many 2fa codes. Ring limits 2fa to 10 codes within 10 minutes. Please try again in 10 minutes.'
177
+ : `Verify that your ${authTypeMessage} correct.`) +
178
+ ` (error: ${responseError})`;
179
+ (0, util_1.logError)(requestError.response || requestError);
180
+ (0, util_1.logError)(errorMessage);
181
+ throw new Error(errorMessage);
182
+ }
183
+ }
184
+ async fetchNewSession(authToken) {
185
+ return requestWithRetry({
186
+ url: clientApi('session'),
187
+ json: {
188
+ device: {
189
+ hardware_id: await this.hardwareIdPromise,
190
+ metadata: this.baseSessionMetadata,
191
+ os: 'android', // can use android, ios, ring-site, windows for sure
192
+ },
193
+ },
194
+ method: 'POST',
195
+ headers: {
196
+ authorization: `Bearer ${authToken.access_token}`,
197
+ },
207
198
  });
208
199
  }
209
200
  getSession() {
210
- return this.authPromise.then((authToken) => __awaiter(this, void 0, void 0, function* () {
201
+ return this.authPromise.then(async (authToken) => {
211
202
  try {
212
- return yield this.fetchNewSession(authToken);
203
+ return await this.fetchNewSession(authToken);
213
204
  }
214
205
  catch (e) {
215
206
  const response = e.response || {};
216
207
  if (response.statusCode === 401) {
217
- yield this.refreshAuth();
208
+ await this.refreshAuth();
218
209
  return this.getSession();
219
210
  }
220
211
  if (response.statusCode === 429) {
@@ -222,78 +213,81 @@ class RingRestClient {
222
213
  ? 200
223
214
  : Number.parseInt(retryAfter, 10);
224
215
  (0, util_1.logError)(`Session response rate limited. Waiting to retry after ${waitSeconds} seconds`);
225
- yield (0, util_1.delay)((waitSeconds + 1) * 1000);
216
+ await (0, util_1.delay)((waitSeconds + 1) * 1000);
226
217
  (0, util_1.logInfo)('Retrying session request');
227
218
  return this.getSession();
228
219
  }
229
220
  throw e;
230
221
  }
231
- }));
232
- }
233
- refreshAuth() {
234
- return __awaiter(this, void 0, void 0, function* () {
235
- this.clearPreviousAuth();
236
- yield this.authPromise;
237
222
  });
238
223
  }
224
+ async refreshAuth() {
225
+ this.clearPreviousAuth();
226
+ await this.authPromise;
227
+ }
239
228
  refreshSession() {
240
229
  this.sessionPromise = this.getSession();
241
230
  }
242
- request(options) {
243
- var _a, _b;
244
- return __awaiter(this, void 0, void 0, function* () {
245
- const hardwareId = yield this.hardwareIdPromise, url = options.url, initialSessionPromise = this.sessionPromise;
246
- try {
247
- yield initialSessionPromise;
248
- const authTokenResponse = yield this.authPromise;
249
- return yield requestWithRetry(Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { authorization: `Bearer ${authTokenResponse.access_token}`, hardware_id: hardwareId, 'User-Agent': 'android:com.ringapp' }) }));
231
+ async request(options) {
232
+ const hardwareId = await this.hardwareIdPromise, url = options.url, initialSessionPromise = this.sessionPromise;
233
+ try {
234
+ await initialSessionPromise;
235
+ const authTokenResponse = await this.authPromise;
236
+ return await requestWithRetry({
237
+ ...options,
238
+ headers: {
239
+ ...options.headers,
240
+ authorization: `Bearer ${authTokenResponse.access_token}`,
241
+ hardware_id: hardwareId,
242
+ 'User-Agent': 'android:com.ringapp',
243
+ },
244
+ });
245
+ }
246
+ catch (e) {
247
+ const response = e.response || {};
248
+ if (response.statusCode === 401) {
249
+ await this.refreshAuth();
250
+ return this.request(options);
250
251
  }
251
- catch (e) {
252
- const response = e.response || {};
253
- if (response.statusCode === 401) {
254
- yield this.refreshAuth();
255
- return this.request(options);
256
- }
257
- if (response.statusCode === 504) {
258
- // Gateway Timeout. These should be recoverable, but wait a few seconds just to be on the safe side
259
- yield (0, util_1.delay)(5000);
252
+ if (response.statusCode === 504) {
253
+ // Gateway Timeout. These should be recoverable, but wait a few seconds just to be on the safe side
254
+ await (0, util_1.delay)(5000);
255
+ return this.request(options);
256
+ }
257
+ if (response.statusCode === 404 &&
258
+ response.body &&
259
+ Array.isArray(response.body.errors)) {
260
+ const errors = response.body.errors, errorText = errors
261
+ .map((code) => ringErrorCodes[code])
262
+ .filter((x) => x)
263
+ .join(', ');
264
+ if (errorText) {
265
+ (0, util_1.logError)(`http request failed. ${url} returned errors: (${errorText}). Trying again in 20 seconds`);
266
+ await (0, util_1.delay)(20000);
260
267
  return this.request(options);
261
268
  }
262
- if (response.statusCode === 404 &&
263
- response.body &&
264
- Array.isArray(response.body.errors)) {
265
- const errors = response.body.errors, errorText = errors
266
- .map((code) => ringErrorCodes[code])
267
- .filter((x) => x)
268
- .join(', ');
269
- if (errorText) {
270
- (0, util_1.logError)(`http request failed. ${url} returned errors: (${errorText}). Trying again in 20 seconds`);
271
- yield (0, util_1.delay)(20000);
272
- return this.request(options);
273
- }
274
- (0, util_1.logError)(`http request failed. ${url} returned unknown errors: (${(0, util_1.stringify)(errors)}).`);
275
- }
276
- if (response.statusCode === 404 && url.startsWith(clientApiBaseUrl)) {
277
- (0, util_1.logError)('404 from endpoint ' + url);
278
- if ((_b = (_a = response.body) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.includes(hardwareId)) {
279
- (0, util_1.logError)('Session hardware_id not found. Creating a new session and trying again.');
280
- if (this.sessionPromise === initialSessionPromise) {
281
- this.refreshSession();
282
- }
283
- return this.request(options);
269
+ (0, util_1.logError)(`http request failed. ${url} returned unknown errors: (${(0, util_1.stringify)(errors)}).`);
270
+ }
271
+ if (response.statusCode === 404 && url.startsWith(clientApiBaseUrl)) {
272
+ (0, util_1.logError)('404 from endpoint ' + url);
273
+ if (response.body?.error?.includes(hardwareId)) {
274
+ (0, util_1.logError)('Session hardware_id not found. Creating a new session and trying again.');
275
+ if (this.sessionPromise === initialSessionPromise) {
276
+ this.refreshSession();
284
277
  }
285
- throw new Error('Not found with response: ' + (0, util_1.stringify)(response.body));
286
- }
287
- if (response.statusCode) {
288
- (0, util_1.logError)(`Request to ${url} failed with status ${response.statusCode}. Response body: ${(0, util_1.stringify)(response.body)}`);
289
- }
290
- else {
291
- (0, util_1.logError)(`Request to ${url} failed:`);
292
- (0, util_1.logError)(e);
278
+ return this.request(options);
293
279
  }
294
- throw e;
280
+ throw new Error('Not found with response: ' + (0, util_1.stringify)(response.body));
295
281
  }
296
- });
282
+ if (response.statusCode) {
283
+ (0, util_1.logError)(`Request to ${url} failed with status ${response.statusCode}. Response body: ${(0, util_1.stringify)(response.body)}`);
284
+ }
285
+ else {
286
+ (0, util_1.logError)(`Request to ${url} failed:`);
287
+ (0, util_1.logError)(e);
288
+ }
289
+ throw e;
290
+ }
297
291
  }
298
292
  getCurrentAuth() {
299
293
  return this.authPromise;