ring-client-api 13.2.1 → 14.0.0-beta.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.
Files changed (39) hide show
  1. package/lib/api.d.ts +10 -8
  2. package/lib/api.js +57 -64
  3. package/lib/device-data.js +6 -9
  4. package/lib/ffmpeg.js +2 -6
  5. package/lib/index.d.ts +7 -7
  6. package/lib/index.js +7 -23
  7. package/lib/location.d.ts +32 -31
  8. package/lib/location.js +67 -72
  9. package/lib/refresh-token.js +6 -11
  10. package/lib/rest-client.d.ts +4 -4
  11. package/lib/rest-client.js +40 -51
  12. package/lib/ring-auth-cli.js +2 -4
  13. package/lib/ring-camera.d.ts +26 -23
  14. package/lib/ring-camera.js +64 -61
  15. package/lib/ring-chime.d.ts +7 -7
  16. package/lib/ring-chime.js +14 -18
  17. package/lib/ring-device-data-cli.js +2 -4
  18. package/lib/ring-device.d.ts +7 -7
  19. package/lib/ring-device.js +16 -20
  20. package/lib/ring-intercom.d.ts +8 -8
  21. package/lib/ring-intercom.js +23 -27
  22. package/lib/ring-types.d.ts +142 -133
  23. package/lib/ring-types.js +137 -153
  24. package/lib/streaming/peer-connection.d.ts +5 -3
  25. package/lib/streaming/peer-connection.js +25 -30
  26. package/lib/streaming/simple-webrtc-session.d.ts +3 -3
  27. package/lib/streaming/simple-webrtc-session.js +3 -7
  28. package/lib/streaming/streaming-messages.d.ts +6 -5
  29. package/lib/streaming/streaming-messages.js +6 -9
  30. package/lib/streaming/streaming-session.d.ts +5 -5
  31. package/lib/streaming/streaming-session.js +34 -38
  32. package/lib/streaming/webrtc-connection.d.ts +5 -5
  33. package/lib/streaming/webrtc-connection.js +39 -43
  34. package/lib/subscribed.d.ts +1 -1
  35. package/lib/subscribed.js +1 -5
  36. package/lib/test/rest-client.spec.js +44 -45
  37. package/lib/test/ring-camera.spec.js +12 -13
  38. package/lib/util.js +33 -54
  39. package/package.json +17 -19
@@ -1,13 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StreamingSession = void 0;
4
- const werift_1 = require("werift");
5
- const camera_utils_1 = require("@homebridge/camera-utils");
6
- const rxjs_1 = require("rxjs");
7
- const ffmpeg_1 = require("../ffmpeg");
8
- const util_1 = require("../util");
9
- const operators_1 = require("rxjs/operators");
10
- const subscribed_1 = require("../subscribed");
1
+ import { RtpPacket } from 'werift';
2
+ import { FfmpegProcess, reservePorts, RtpSplitter, } from '@homebridge/camera-utils';
3
+ import { firstValueFrom, ReplaySubject, Subject } from 'rxjs';
4
+ import { getFfmpegPath } from "../ffmpeg.js";
5
+ import { logDebug, logError } from "../util.js";
6
+ import { concatMap, map, mergeWith, take } from 'rxjs/operators';
7
+ import { Subscribed } from "../subscribed.js";
11
8
  function getCleanSdp(sdp, includeVideo) {
12
9
  return sdp
13
10
  .split('\nm=')
@@ -16,16 +13,16 @@ function getCleanSdp(sdp, includeVideo) {
16
13
  .filter((section) => includeVideo || !section.startsWith('m=video'))
17
14
  .join('\n');
18
15
  }
19
- class StreamingSession extends subscribed_1.Subscribed {
16
+ export class StreamingSession extends Subscribed {
17
+ onCallEnded = new ReplaySubject(1);
18
+ onUsingOpus = new ReplaySubject(1);
19
+ onVideoRtp = new Subject();
20
+ onAudioRtp = new Subject();
21
+ audioSplitter = new RtpSplitter();
22
+ videoSplitter = new RtpSplitter();
23
+ returnAudioSplitter = new RtpSplitter();
20
24
  camera;
21
25
  connection;
22
- onCallEnded = new rxjs_1.ReplaySubject(1);
23
- onUsingOpus = new rxjs_1.ReplaySubject(1);
24
- onVideoRtp = new rxjs_1.Subject();
25
- onAudioRtp = new rxjs_1.Subject();
26
- audioSplitter = new camera_utils_1.RtpSplitter();
27
- videoSplitter = new camera_utils_1.RtpSplitter();
28
- returnAudioSplitter = new camera_utils_1.RtpSplitter();
29
26
  constructor(camera, connection) {
30
27
  super();
31
28
  this.camera = camera;
@@ -53,11 +50,11 @@ class StreamingSession extends subscribed_1.Subscribed {
53
50
  this.connection.activateCameraSpeaker();
54
51
  }
55
52
  async reservePort(bufferPorts = 0) {
56
- const ports = await (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
53
+ const ports = await reservePorts({ count: bufferPorts + 1 });
57
54
  return ports[0];
58
55
  }
59
56
  get isUsingOpus() {
60
- return (0, rxjs_1.firstValueFrom)(this.onUsingOpus.pipe((0, operators_1.mergeWith)(this.connection.onError.pipe((0, operators_1.map)((e) => {
57
+ return firstValueFrom(this.onUsingOpus.pipe(mergeWith(this.connection.onError.pipe(map((e) => {
61
58
  throw e;
62
59
  })))));
63
60
  }
@@ -66,11 +63,11 @@ class StreamingSession extends subscribed_1.Subscribed {
66
63
  return;
67
64
  }
68
65
  const videoPort = await this.reservePort(1), audioPort = await this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, ringSdp = await Promise.race([
69
- (0, rxjs_1.firstValueFrom)(this.connection.onCallAnswered),
70
- (0, rxjs_1.firstValueFrom)(this.onCallEnded),
66
+ firstValueFrom(this.connection.onCallAnswered),
67
+ firstValueFrom(this.onCallEnded),
71
68
  ]);
72
69
  if (!ringSdp) {
73
- (0, util_1.logDebug)('Call ended before answered');
70
+ logDebug('Call ended before answered');
74
71
  return;
75
72
  }
76
73
  const usingOpus = await this.isUsingOpus, ffmpegInputArguments = [
@@ -86,21 +83,21 @@ class StreamingSession extends subscribed_1.Subscribed {
86
83
  'pipe:',
87
84
  ], inputSdp = getCleanSdp(ringSdp, transcodeVideoStream)
88
85
  .replace(/m=audio \d+/, `m=audio ${audioPort}`)
89
- .replace(/m=video \d+/, `m=video ${videoPort}`), ff = new camera_utils_1.FfmpegProcess({
86
+ .replace(/m=video \d+/, `m=video ${videoPort}`), ff = new FfmpegProcess({
90
87
  ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
91
88
  ? ffmpegOptions.video || ['-vcodec', 'copy']
92
89
  : []), ...(ffmpegOptions.output || [])),
93
- ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
90
+ ffmpegPath: getFfmpegPath(),
94
91
  stdoutCallback: ffmpegOptions.stdoutCallback,
95
92
  exitCallback: () => this.callEnded(),
96
93
  logLabel: `From Ring (${this.camera.name})`,
97
94
  logger: {
98
- error: util_1.logError,
99
- info: util_1.logDebug,
95
+ error: logError,
96
+ info: logDebug,
100
97
  },
101
98
  });
102
99
  this.addSubscriptions(this.onAudioRtp
103
- .pipe((0, operators_1.concatMap)((rtp) => {
100
+ .pipe(concatMap((rtp) => {
104
101
  return this.audioSplitter.send(rtp.serialize(), {
105
102
  port: audioPort,
106
103
  });
@@ -108,14 +105,14 @@ class StreamingSession extends subscribed_1.Subscribed {
108
105
  .subscribe());
109
106
  if (transcodeVideoStream) {
110
107
  this.addSubscriptions(this.onVideoRtp
111
- .pipe((0, operators_1.concatMap)((rtp) => {
108
+ .pipe(concatMap((rtp) => {
112
109
  return this.videoSplitter.send(rtp.serialize(), {
113
110
  port: videoPort,
114
111
  });
115
112
  }))
116
113
  .subscribe());
117
114
  }
118
- this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
115
+ this.onCallEnded.pipe(take(1)).subscribe(() => ff.stop());
119
116
  ff.writeStdin(inputSdp);
120
117
  // Request a key frame now that ffmpeg is ready to receive
121
118
  this.requestKeyFrame();
@@ -124,11 +121,11 @@ class StreamingSession extends subscribed_1.Subscribed {
124
121
  if (this.hasEnded) {
125
122
  return;
126
123
  }
127
- const audioOutForwarder = new camera_utils_1.RtpSplitter(({ message }) => {
128
- const rtp = werift_1.RtpPacket.deSerialize(message);
124
+ const audioOutForwarder = new RtpSplitter(({ message }) => {
125
+ const rtp = RtpPacket.deSerialize(message);
129
126
  this.connection.sendAudioPacket(rtp);
130
127
  return null;
131
- }), usingOpus = await this.isUsingOpus, ff = new camera_utils_1.FfmpegProcess({
128
+ }), usingOpus = await this.isUsingOpus, ff = new FfmpegProcess({
132
129
  ffmpegArgs: [
133
130
  '-hide_banner',
134
131
  '-protocol_whitelist',
@@ -146,15 +143,15 @@ class StreamingSession extends subscribed_1.Subscribed {
146
143
  'rtp',
147
144
  `rtp://127.0.0.1:${await audioOutForwarder.portPromise}`,
148
145
  ],
149
- ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
146
+ ffmpegPath: getFfmpegPath(),
150
147
  exitCallback: () => this.callEnded(),
151
148
  logLabel: `Return Audio (${this.camera.name})`,
152
149
  logger: {
153
- error: util_1.logError,
154
- info: util_1.logDebug,
150
+ error: logError,
151
+ info: logDebug,
155
152
  },
156
153
  });
157
- this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
154
+ this.onCallEnded.pipe(take(1)).subscribe(() => ff.stop());
158
155
  }
159
156
  hasEnded = false;
160
157
  callEnded() {
@@ -182,4 +179,3 @@ class StreamingSession extends subscribed_1.Subscribed {
182
179
  this.connection.requestKeyFrame();
183
180
  }
184
181
  }
185
- exports.StreamingSession = StreamingSession;
@@ -1,13 +1,12 @@
1
1
  import { ReplaySubject, Subject } from 'rxjs';
2
- import { RingCamera } from '../ring-camera';
3
- import { BasicPeerConnection } from './peer-connection';
4
- import { Subscribed } from '../subscribed';
5
- import { RtpPacket } from 'werift';
2
+ import type { RingCamera } from '../ring-camera.ts';
3
+ import { type BasicPeerConnection } from './peer-connection.ts';
4
+ import { Subscribed } from '../subscribed.ts';
5
+ import type { RtpPacket } from 'werift';
6
6
  export interface StreamingConnectionOptions {
7
7
  createPeerConnection?: () => BasicPeerConnection;
8
8
  }
9
9
  export declare class WebrtcConnection extends Subscribed {
10
- private camera;
11
10
  private readonly onSessionId;
12
11
  private readonly onOfferSent;
13
12
  private readonly dialogId;
@@ -23,6 +22,7 @@ export declare class WebrtcConnection extends Subscribed {
23
22
  readonly onVideoRtp: Subject<RtpPacket>;
24
23
  private readonly pc;
25
24
  private readonly ws;
25
+ private camera;
26
26
  constructor(ticket: string, camera: RingCamera, options: StreamingConnectionOptions);
27
27
  private initiateCall;
28
28
  private sessionId;
@@ -1,31 +1,28 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebrtcConnection = void 0;
4
- const ws_1 = require("ws");
5
- const rxjs_1 = require("rxjs");
6
- const operators_1 = require("rxjs/operators");
7
- const util_1 = require("../util");
8
- const peer_connection_1 = require("./peer-connection");
9
- const subscribed_1 = require("../subscribed");
10
- class WebrtcConnection extends subscribed_1.Subscribed {
11
- camera;
12
- onSessionId = new rxjs_1.ReplaySubject(1);
13
- onOfferSent = new rxjs_1.ReplaySubject(1);
14
- dialogId = (0, util_1.generateUuid)();
15
- onCameraConnected = new rxjs_1.ReplaySubject(1);
16
- onCallAnswered = new rxjs_1.ReplaySubject(1);
17
- onCallEnded = new rxjs_1.ReplaySubject(1);
18
- onError = new rxjs_1.ReplaySubject(1);
19
- onMessage = new rxjs_1.ReplaySubject();
1
+ import { WebSocket } from 'ws';
2
+ import { firstValueFrom, fromEvent, interval, ReplaySubject, Subject, } from 'rxjs';
3
+ import { concatMap, take } from 'rxjs/operators';
4
+ import { generateUuid, logDebug, logError, logInfo } from "../util.js";
5
+ import { WeriftPeerConnection, } from "./peer-connection.js";
6
+ import { Subscribed } from "../subscribed.js";
7
+ export class WebrtcConnection extends Subscribed {
8
+ onSessionId = new ReplaySubject(1);
9
+ onOfferSent = new ReplaySubject(1);
10
+ dialogId = generateUuid();
11
+ onCameraConnected = new ReplaySubject(1);
12
+ onCallAnswered = new ReplaySubject(1);
13
+ onCallEnded = new ReplaySubject(1);
14
+ onError = new ReplaySubject(1);
15
+ onMessage = new ReplaySubject();
20
16
  onWsOpen;
21
17
  onAudioRtp;
22
18
  onVideoRtp;
23
19
  pc;
24
20
  ws;
21
+ camera;
25
22
  constructor(ticket, camera, options) {
26
23
  super();
27
24
  this.camera = camera;
28
- this.ws = new ws_1.WebSocket(`wss://api.prod.signalling.ring.devices.a2z.com:443/ws?api_version=4.0&auth_type=ring_solutions&client_id=ring_site-${(0, util_1.generateUuid)()}&token=${ticket}`, {
25
+ this.ws = new WebSocket(`wss://api.prod.signalling.ring.devices.a2z.com:443/ws?api_version=4.0&auth_type=ring_solutions&client_id=ring_site-${generateUuid()}&token=${ticket}`, {
29
26
  headers: {
30
27
  // This must exist or the socket will close immediately but content does not seem to matter
31
28
  'User-Agent': 'android:com.ringapp',
@@ -35,20 +32,20 @@ class WebrtcConnection extends subscribed_1.Subscribed {
35
32
  // we were passed a custom peer connection factory
36
33
  this.pc = options.createPeerConnection();
37
34
  // passing rtp packets is not supported for custom peer connections
38
- this.onAudioRtp = new rxjs_1.Subject();
39
- this.onVideoRtp = new rxjs_1.Subject();
35
+ this.onAudioRtp = new Subject();
36
+ this.onVideoRtp = new Subject();
40
37
  }
41
38
  else {
42
39
  // no custom peer connection factory, use the werift and pass along rtp packets
43
- const pc = new peer_connection_1.WeriftPeerConnection();
40
+ const pc = new WeriftPeerConnection();
44
41
  this.pc = pc;
45
42
  this.onAudioRtp = pc.onAudioRtp;
46
43
  this.onVideoRtp = pc.onVideoRtp;
47
44
  }
48
- this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
49
- const onMessage = (0, rxjs_1.fromEvent)(this.ws, 'message'), onError = (0, rxjs_1.fromEvent)(this.ws, 'error'), onClose = (0, rxjs_1.fromEvent)(this.ws, 'close');
45
+ this.onWsOpen = fromEvent(this.ws, 'open');
46
+ const onMessage = fromEvent(this.ws, 'message'), onError = fromEvent(this.ws, 'error'), onClose = fromEvent(this.ws, 'close');
50
47
  this.addSubscriptions(onMessage
51
- .pipe((0, operators_1.concatMap)((event) => {
48
+ .pipe(concatMap((event) => {
52
49
  const message = JSON.parse(event.data);
53
50
  this.onMessage.next(message);
54
51
  return this.handleMessage(message).catch((e) => {
@@ -61,35 +58,35 @@ class WebrtcConnection extends subscribed_1.Subscribed {
61
58
  });
62
59
  }))
63
60
  .subscribe(), onError.subscribe((e) => {
64
- (0, util_1.logError)(e);
61
+ logError(e);
65
62
  this.callEnded();
66
63
  }), onClose.subscribe(() => {
67
64
  this.callEnded();
68
65
  }), this.pc.onConnectionState.subscribe((state) => {
69
66
  if (state === 'failed') {
70
- (0, util_1.logError)('Stream connection failed');
67
+ logError('Stream connection failed');
71
68
  this.callEnded();
72
69
  }
73
70
  if (state === 'closed') {
74
- (0, util_1.logDebug)('Stream connection closed');
71
+ logDebug('Stream connection closed');
75
72
  this.callEnded();
76
73
  }
77
74
  }), this.onError.subscribe((e) => {
78
- (0, util_1.logError)(e);
75
+ logError(e);
79
76
  this.callEnded();
80
77
  }), this.onWsOpen.subscribe(() => {
81
78
  const connectionType = camera.isRingEdgeEnabled ? 'Ring Edge' : 'Cloud';
82
- (0, util_1.logDebug)(`WebSocket connected for ${camera.name} (${connectionType})`);
79
+ logDebug(`WebSocket connected for ${camera.name} (${connectionType})`);
83
80
  this.initiateCall().catch((e) => {
84
- (0, util_1.logError)(e);
81
+ logError(e);
85
82
  this.callEnded();
86
83
  });
87
84
  }),
88
85
  // The ring-edge session needs a ping every 5 seconds to keep the connection alive
89
- (0, rxjs_1.interval)(5000).subscribe(() => {
86
+ interval(5000).subscribe(() => {
90
87
  this.sendSessionMessage('ping');
91
88
  }), this.pc.onIceCandidate.subscribe(async (iceCandidate) => {
92
- await (0, rxjs_1.firstValueFrom)(this.onOfferSent);
89
+ await firstValueFrom(this.onOfferSent);
93
90
  this.sendMessage({
94
91
  method: 'ice',
95
92
  dialog_id: this.dialogId,
@@ -161,8 +158,8 @@ class WebrtcConnection extends subscribed_1.Subscribed {
161
158
  break;
162
159
  }
163
160
  case 'close':
164
- (0, util_1.logError)('Video stream closed');
165
- (0, util_1.logError)(message.body);
161
+ logError('Video stream closed');
162
+ logError(message.body);
166
163
  this.callEnded();
167
164
  return;
168
165
  case 'camera_started':
@@ -170,8 +167,8 @@ class WebrtcConnection extends subscribed_1.Subscribed {
170
167
  // ignore these messages as we don't use them
171
168
  return;
172
169
  }
173
- (0, util_1.logError)('UNKNOWN MESSAGE');
174
- (0, util_1.logError)(message);
170
+ logError('UNKNOWN MESSAGE');
171
+ logError(message);
175
172
  }
176
173
  sendSessionMessage(method, body = {}) {
177
174
  const sendSessionMessage = (sessionId) => {
@@ -193,7 +190,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
193
190
  }
194
191
  else {
195
192
  // Otherwise wait for the session id to be set and then send the message
196
- this.addSubscriptions(this.onSessionId.pipe((0, operators_1.take)(1)).subscribe(sendSessionMessage));
193
+ this.addSubscriptions(this.onSessionId.pipe(take(1)).subscribe(sendSessionMessage));
197
194
  }
198
195
  }
199
196
  sendMessage(message) {
@@ -206,7 +203,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
206
203
  if (this.hasEnded) {
207
204
  return;
208
205
  }
209
- if (this.pc instanceof peer_connection_1.WeriftPeerConnection) {
206
+ if (this.pc instanceof WeriftPeerConnection) {
210
207
  this.pc.returnAudioTrack.writeRtp(rtp);
211
208
  }
212
209
  else {
@@ -214,7 +211,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
214
211
  }
215
212
  }
216
213
  activate() {
217
- (0, util_1.logInfo)('Activating Session');
214
+ logInfo('Activating Session');
218
215
  // the activate_session message is required to keep the stream alive longer than 70 seconds
219
216
  this.sendSessionMessage('activate_session');
220
217
  this.sendSessionMessage('stream_options', {
@@ -224,7 +221,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
224
221
  }
225
222
  activateCameraSpeaker() {
226
223
  // Fire and forget this call so that callers don't get hung up waiting for answer (which might not happen)
227
- this.addSubscriptions(this.onCameraConnected.pipe((0, operators_1.take)(1)).subscribe(() => {
224
+ this.addSubscriptions(this.onCameraConnected.pipe(take(1)).subscribe(() => {
228
225
  this.sendSessionMessage('camera_options', {
229
226
  stealth_mode: false,
230
227
  });
@@ -257,4 +254,3 @@ class WebrtcConnection extends subscribed_1.Subscribed {
257
254
  this.pc.requestKeyFrame?.();
258
255
  }
259
256
  }
260
- exports.WebrtcConnection = WebrtcConnection;
@@ -1,4 +1,4 @@
1
- import { Subscription } from 'rxjs';
1
+ import type { Subscription } from 'rxjs';
2
2
  export declare class Subscribed {
3
3
  private readonly subscriptions;
4
4
  addSubscriptions(...subscriptions: Subscription[]): void;
package/lib/subscribed.js CHANGED
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Subscribed = void 0;
4
- class Subscribed {
1
+ export class Subscribed {
5
2
  subscriptions = [];
6
3
  addSubscriptions(...subscriptions) {
7
4
  this.subscriptions.push(...subscriptions);
@@ -10,4 +7,3 @@ class Subscribed {
10
7
  this.subscriptions.forEach((subscription) => subscription.unsubscribe());
11
8
  }
12
9
  }
13
- exports.Subscribed = Subscribed;
@@ -1,17 +1,16 @@
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");
1
+ import { http, HttpResponse } from 'msw';
2
+ import { setupServer } from 'msw/node';
3
+ import { RingRestClient } from "../rest-client.js";
4
+ import { clearTimeouts, getHardwareId, toBase64 } from "../util.js";
5
+ import { firstValueFrom } from 'rxjs';
6
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, } from 'vitest';
8
7
  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 }) => {
8
+ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoFactorAuthCode = '123456', hardwareIdPromise = 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 = setupServer(http.post('https://oauth.ring.com/oauth/token', async ({ request: req }) => {
10
9
  const body = await req.json();
11
10
  if (req.headers.get('2fa-support') !== 'true' ||
12
11
  req.headers.get('User-Agent') !== 'android:com.ringapp' ||
13
12
  req.headers.get('hardware_id') !== (await hardwareIdPromise)) {
14
- return msw_1.HttpResponse.json({
13
+ return HttpResponse.json({
15
14
  code: 1,
16
15
  error: 'Invalid auth headers: ' + JSON.stringify(req.headers, null, 2),
17
16
  }, { status: 400 });
@@ -19,7 +18,7 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
19
18
  if (body.grant_type === 'refresh_token') {
20
19
  if (body.refresh_token === refreshToken) {
21
20
  // Valid refresh token
22
- return msw_1.HttpResponse.json({
21
+ return HttpResponse.json({
23
22
  access_token: accessToken,
24
23
  expires_in: 3600,
25
24
  refresh_token: secondRefreshToken,
@@ -29,7 +28,7 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
29
28
  }
30
29
  if (body.refresh_token === secondRefreshToken) {
31
30
  // Valid refresh token
32
- return msw_1.HttpResponse.json({
31
+ return HttpResponse.json({
33
32
  access_token: secondAccessToken,
34
33
  expires_in: 3600,
35
34
  refresh_token: thirdRefreshToken,
@@ -38,7 +37,7 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
38
37
  }, { status: 200 });
39
38
  }
40
39
  // Invalid refresh token
41
- return msw_1.HttpResponse.json({
40
+ return HttpResponse.json({
42
41
  error: 'invalid_grant',
43
42
  error_description: 'token is invalid or does not exists',
44
43
  }, { status: 401 });
@@ -46,14 +45,14 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
46
45
  if (body.grant_type !== 'password' ||
47
46
  body.client_id !== 'ring_official_android' ||
48
47
  body.scope !== 'client') {
49
- return msw_1.HttpResponse.json({
48
+ return HttpResponse.json({
50
49
  code: 1,
51
50
  error: 'Invalid auth request: ' + JSON.stringify(body),
52
51
  }, { status: 400 });
53
52
  }
54
53
  if (body.username !== email || body.password !== password) {
55
54
  // Wrong username or password
56
- return msw_1.HttpResponse.json({
55
+ return HttpResponse.json({
57
56
  error: 'access_denied',
58
57
  error_description: 'invalid user credentials',
59
58
  }, { status: 401 });
@@ -61,14 +60,14 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
61
60
  if (req.headers.get('2fa-code') &&
62
61
  req.headers.get('2fa-code') !== twoFactorAuthCode) {
63
62
  // Wrong 2fa code
64
- return msw_1.HttpResponse.json({
63
+ return HttpResponse.json({
65
64
  err_msg: 'bad request response from dependency service',
66
65
  error: 'Verification Code is invalid or expired',
67
66
  }, { status: 400 });
68
67
  }
69
68
  if (req.headers.get('2fa-code') === twoFactorAuthCode) {
70
69
  // Successfull login with correct 2fa code
71
- return msw_1.HttpResponse.json({
70
+ return HttpResponse.json({
72
71
  access_token: accessToken,
73
72
  expires_in: 3600,
74
73
  refresh_token: refreshToken,
@@ -78,35 +77,35 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
78
77
  });
79
78
  }
80
79
  // 2fa code not provided, so return the 2fa prompt
81
- return msw_1.HttpResponse.json({
80
+ return HttpResponse.json({
82
81
  next_time_in_secs: 60,
83
82
  phone,
84
83
  tsv_state: 'sms',
85
84
  }, { status: 412 });
86
- }), msw_1.http.post('https://api.ring.com/clients_api/session', async ({ request: req }) => {
85
+ }), http.post('https://api.ring.com/clients_api/session', async ({ request: req }) => {
87
86
  const authHeader = req.headers.get('Authorization');
88
87
  if (authHeader !== `Bearer ${accessToken}` &&
89
88
  authHeader !== `Bearer ${secondAccessToken}`) {
90
89
  // Invalid access token used
91
- return msw_1.HttpResponse.json({}, { status: 401 });
90
+ return HttpResponse.json({}, { status: 401 });
92
91
  }
93
92
  const body = await req.json();
94
- if (body.device.hardware_id !== (await (0, util_1.getHardwareId)()) ||
93
+ if (body.device.hardware_id !== (await getHardwareId()) ||
95
94
  body.device.metadata.api_version !== 11 ||
96
95
  body.device.metadata.device_model !== 'ring-client-api' ||
97
96
  body.device.os !== 'android') {
98
- return msw_1.HttpResponse.text('Bad session request: ' + JSON.stringify(body, null, 2), { status: 400 });
97
+ return HttpResponse.text('Bad session request: ' + JSON.stringify(body, null, 2), { status: 400 });
99
98
  }
100
99
  // Fake a response from the session endpoint, incrementing the sessionCreatedCount
101
100
  sessionCreatedCount++;
102
- return msw_1.HttpResponse.json({
101
+ return HttpResponse.json({
103
102
  profile: {
104
103
  id: 1234,
105
104
  },
106
105
  });
107
106
  }));
108
107
  async function wrapRefreshToken(rt) {
109
- return (0, util_1.toBase64)(JSON.stringify({
108
+ return toBase64(JSON.stringify({
110
109
  rt,
111
110
  hid: await hardwareIdPromise,
112
111
  }));
@@ -125,11 +124,11 @@ afterAll(() => {
125
124
  });
126
125
  afterEach(() => {
127
126
  client.clearTimeouts();
128
- (0, util_1.clearTimeouts)();
127
+ clearTimeouts();
129
128
  });
130
129
  describe('getAuth', () => {
131
- test('It should throw and set the 2fa prompt', async () => {
132
- client = new rest_client_1.RingRestClient({
130
+ it('should throw and set the 2fa prompt', async () => {
131
+ client = new RingRestClient({
133
132
  password,
134
133
  email,
135
134
  });
@@ -137,8 +136,8 @@ describe('getAuth', () => {
137
136
  expect(client.promptFor2fa).toEqual(`Please enter the code sent to ${phone} via sms`);
138
137
  expect(client.using2fa).toEqual(true);
139
138
  });
140
- test('It should accept a 2fa code', async () => {
141
- client = new rest_client_1.RingRestClient({
139
+ it('should accept a 2fa code', async () => {
140
+ client = new RingRestClient({
142
141
  password,
143
142
  email,
144
143
  });
@@ -152,26 +151,26 @@ describe('getAuth', () => {
152
151
  });
153
152
  expect(client.refreshToken).toEqual(await wrapRefreshToken(refreshToken));
154
153
  });
155
- test('it should handle invalid credentials', async () => {
156
- client = new rest_client_1.RingRestClient({
154
+ it('should handle invalid credentials', async () => {
155
+ client = new RingRestClient({
157
156
  password: 'incorrect password',
158
157
  email,
159
158
  });
160
159
  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
160
  });
162
- test('it should handle invalid 2fa code', async () => {
163
- client = new rest_client_1.RingRestClient({
161
+ it('should handle invalid 2fa code', async () => {
162
+ client = new RingRestClient({
164
163
  password,
165
164
  email,
166
165
  });
167
- // ignore the first reject, it's tested above
166
+ // ignore the first reject, it's it('ove
168
167
  await expect(() => client.getAuth()).rejects.toThrow();
169
168
  // call getAuth again with an invalid 2fa code, which should fail
170
169
  await expect(() => client.getAuth('invalid 2fa code')).rejects.toThrow('Verification Code is invalid or expired');
171
170
  expect(client.promptFor2fa).toEqual('Invalid 2fa code entered. Please try again.');
172
171
  });
173
- test('it should establish a valid auth token with a valid refresh token', async () => {
174
- client = new rest_client_1.RingRestClient({
172
+ it('should establish a valid auth token with a valid refresh token', async () => {
173
+ client = new RingRestClient({
175
174
  refreshToken,
176
175
  });
177
176
  expect(await client.getCurrentAuth()).toMatchObject({
@@ -180,11 +179,11 @@ describe('getAuth', () => {
180
179
  });
181
180
  expect(client.refreshToken).toEqual(await wrapRefreshToken(secondRefreshToken));
182
181
  });
183
- test('it should emit an event when a new refresh token is created', async () => {
184
- client = new rest_client_1.RingRestClient({
182
+ it('should emit an event when a new refresh token is created', async () => {
183
+ client = new RingRestClient({
185
184
  refreshToken,
186
185
  });
187
- const refreshedPromise = (0, rxjs_1.firstValueFrom)(client.onRefreshTokenUpdated), auth = await client.getAuth();
186
+ const refreshedPromise = firstValueFrom(client.onRefreshTokenUpdated), auth = await client.getAuth();
188
187
  expect(auth).toMatchObject({
189
188
  access_token: accessToken,
190
189
  refresh_token: await wrapRefreshToken(secondRefreshToken),
@@ -199,29 +198,29 @@ describe('fetch', () => {
199
198
  let invalidateFirstAccessToken = false;
200
199
  beforeEach(() => {
201
200
  invalidateFirstAccessToken = false;
202
- server.use(msw_1.http.get('https://api.ring.com/clients_api/some_endpoint', ({ request: req }) => {
201
+ server.use(http.get('https://api.ring.com/clients_api/some_endpoint', ({ request: req }) => {
203
202
  const authHeader = req.headers.get('Authorization');
204
203
  if (invalidateFirstAccessToken &&
205
204
  authHeader === `Bearer ${accessToken}`) {
206
205
  // Original access token used, but no longer valid
207
- return msw_1.HttpResponse.json({}, { status: 401 });
206
+ return HttpResponse.json({}, { status: 401 });
208
207
  }
209
208
  if (authHeader !== `Bearer ${accessToken}` &&
210
209
  authHeader !== `Bearer ${secondAccessToken}`) {
211
210
  // Invalid access token used
212
- return msw_1.HttpResponse.json({}, { status: 401 });
211
+ return HttpResponse.json({}, { status: 401 });
213
212
  }
214
213
  if (sessionCreatedCount === 0) {
215
214
  // Session not created yet
216
- return msw_1.HttpResponse.json({
215
+ return HttpResponse.json({
217
216
  error: 'Session not found for ' + req.headers.get('hardware_id'),
218
217
  }, { status: 404 });
219
218
  }
220
- return msw_1.HttpResponse.json([]);
219
+ return HttpResponse.json([]);
221
220
  }));
222
221
  });
223
222
  it('should include the auth token as a header', async () => {
224
- client = new rest_client_1.RingRestClient({
223
+ client = new RingRestClient({
225
224
  refreshToken,
226
225
  });
227
226
  const response = await client.request({
@@ -230,7 +229,7 @@ describe('fetch', () => {
230
229
  expect(response).toEqual([]);
231
230
  });
232
231
  it('should fetch a new auth token if the first is no longer valid', async () => {
233
- client = new rest_client_1.RingRestClient({
232
+ client = new RingRestClient({
234
233
  refreshToken,
235
234
  });
236
235
  invalidateFirstAccessToken = true;