ring-client-api 9.23.0 → 10.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.
@@ -14,12 +14,10 @@ const ring_types_1 = require("./ring-types");
14
14
  const rest_client_1 = require("./rest-client");
15
15
  const rxjs_1 = require("rxjs");
16
16
  const operators_1 = require("rxjs/operators");
17
- const camera_utils_1 = require("@homebridge/camera-utils");
18
17
  const util_1 = require("./util");
19
- const sip_session_1 = require("./sip-session");
20
18
  const subscribed_1 = require("./subscribed");
21
- const snapshotRefreshDelay = 500, maxSnapshotRefreshSeconds = 35, // needs to be 30+ because battery cam can't take snapshot while recording
22
- maxSnapshotRefreshAttempts = (maxSnapshotRefreshSeconds * 1000) / snapshotRefreshDelay, fullDayMs = 24 * 60 * 60 * 1000;
19
+ const live_call_1 = require("./live-call");
20
+ const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
23
21
  function parseBatteryLife(batteryLife) {
24
22
  if (batteryLife === null || batteryLife === undefined) {
25
23
  return null;
@@ -73,17 +71,6 @@ class RingCamera extends subscribed_1.Subscribed {
73
71
  this.restClient = restClient;
74
72
  this.avoidSnapshotBatteryDrain = avoidSnapshotBatteryDrain;
75
73
  this.treatKnockAsDing = treatKnockAsDing;
76
- this.id = this.initialData.id;
77
- this.deviceType = this.initialData.kind;
78
- this.model = ring_types_1.RingCameraModel[this.initialData.kind] || 'Unknown Model';
79
- this.onData = new rxjs_1.BehaviorSubject(this.initialData);
80
- this.hasLight = this.initialData.led_status !== undefined;
81
- this.hasSiren = this.initialData.siren_status !== undefined;
82
- this.hasBattery = (0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
83
- (typeof this.initialData.battery_life === 'string' &&
84
- this.batteryLevel !== null &&
85
- this.batteryLevel < 100 &&
86
- this.batteryLevel >= 0);
87
74
  this.onRequestUpdate = new rxjs_1.Subject();
88
75
  this.onRequestActiveDings = new rxjs_1.Subject();
89
76
  this.onNewDing = new rxjs_1.Subject();
@@ -93,12 +80,19 @@ class RingCamera extends subscribed_1.Subscribed {
93
80
  this.onMotionDetected = this.onActiveDings.pipe((0, operators_1.map)((dings) => dings.some((ding) => ding.motion || ding.kind === 'motion')), (0, operators_1.distinctUntilChanged)(), (0, operators_1.publishReplay)(1), (0, operators_1.refCount)());
94
81
  this.onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.mapTo)(null), // no value needed, event is what matters
95
82
  (0, operators_1.share)());
83
+ this.lastSnapshotTimestamp = 0;
84
+ this.lastSnapshotTimestampLocal = 0;
85
+ this.fetchingSnapshot = false;
86
+ this.id = this.initialData.id;
87
+ this.deviceType = this.initialData.kind;
88
+ this.model = ring_types_1.RingCameraModel[this.initialData.kind] || 'Unknown Model';
89
+ this.onData = new rxjs_1.BehaviorSubject(this.initialData);
90
+ this.hasLight = this.initialData.led_status !== undefined;
91
+ this.hasSiren = this.initialData.siren_status !== undefined;
96
92
  this.onBatteryLevel = this.onData.pipe((0, operators_1.map)(getBatteryLevel), (0, operators_1.distinctUntilChanged)());
97
93
  this.onInHomeDoorbellStatus = this.onData.pipe((0, operators_1.map)(({ settings: { chime_settings } }) => {
98
94
  return Boolean(chime_settings === null || chime_settings === void 0 ? void 0 : chime_settings.enable);
99
95
  }), (0, operators_1.distinctUntilChanged)());
100
- this.expiredDingIds = [];
101
- this.lastSnapshotTimestampLocal = 0;
102
96
  if (!initialData.subscribed) {
103
97
  this.subscribeToDingEvents().catch((e) => {
104
98
  (0, util_1.logError)('Failed to subscribe ' + initialData.description + ' to ding events');
@@ -130,6 +124,15 @@ class RingCamera extends subscribed_1.Subscribed {
130
124
  get batteryLevel() {
131
125
  return getBatteryLevel(this.data);
132
126
  }
127
+ get hasBattery() {
128
+ if (this.batteryLevel === null) {
129
+ return false;
130
+ }
131
+ return ((0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
132
+ (typeof this.initialData.battery_life === 'string' &&
133
+ this.batteryLevel < 100 &&
134
+ this.batteryLevel >= 0));
135
+ }
133
136
  get hasLowBattery() {
134
137
  return this.data.alerts.battery === 'low';
135
138
  }
@@ -226,40 +229,23 @@ class RingCamera extends subscribed_1.Subscribed {
226
229
  return response.device_health;
227
230
  });
228
231
  }
229
- startVideoOnDemand() {
230
- return this.restClient
231
- .request({
232
- method: 'POST',
233
- url: this.doorbotUrl('live_view'), // Ring app uses vod for battery cams, but doesn't appear to be necessary
234
- })
235
- .catch((e) => {
236
- var _a;
237
- if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.statusCode) === 403) {
238
- const errorMessage = `Camera ${this.name} returned 403 when starting a live stream. This usually indicates that live streaming is blocked by Modes settings. Check your Ring app and verify that you are able to stream from this camera with the current Modes settings.`;
239
- (0, util_1.logError)(errorMessage);
240
- throw new Error(errorMessage);
241
- }
242
- throw e;
243
- });
244
- }
245
- pollForActiveDing() {
246
- // try every second until a new ding is received
247
- this.addSubscriptions((0, rxjs_1.interval)(1000)
248
- .pipe((0, operators_1.takeUntil)(this.onNewDing))
249
- .subscribe(() => {
250
- this.onRequestActiveDings.next(null);
251
- }));
252
- }
253
- getSipConnectionDetails() {
232
+ startLiveCall() {
254
233
  return __awaiter(this, void 0, void 0, function* () {
255
- const vodPromise = (0, rxjs_1.firstValueFrom)(this.onNewDing), videoOnDemandDing = yield this.startVideoOnDemand();
256
- if (videoOnDemandDing && 'sip_from' in videoOnDemandDing) {
257
- // wired cams return a ding from live_view so we don't need to wait
258
- return videoOnDemandDing;
259
- }
260
- // battery cams return '' from live_view so we need to request active dings and wait
261
- this.pollForActiveDing();
262
- return vodPromise;
234
+ const liveCall = yield this.restClient
235
+ .request({
236
+ method: 'POST',
237
+ url: this.doorbotUrl('live_call'),
238
+ })
239
+ .catch((e) => {
240
+ var _a;
241
+ if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.statusCode) === 403) {
242
+ const errorMessage = `Camera ${this.name} returned 403 when starting a live stream. This usually indicates that live streaming is blocked by Modes settings. Check your Ring app and verify that you are able to stream from this camera with the current Modes settings.`;
243
+ (0, util_1.logError)(errorMessage);
244
+ throw new Error(errorMessage);
245
+ }
246
+ throw e;
247
+ });
248
+ return new live_call_1.LiveCall(liveCall.data.session_id, this);
263
249
  });
264
250
  }
265
251
  removeDingById(idToRemove) {
@@ -272,7 +258,6 @@ class RingCamera extends subscribed_1.Subscribed {
272
258
  this.onNewDing.next(ding);
273
259
  setTimeout(() => {
274
260
  this.removeDingById(ding.id_str);
275
- this.expiredDingIds = this.expiredDingIds.filter((id) => id !== dingId);
276
261
  }, 65 * 1000); // dings last ~1 minute
277
262
  }
278
263
  getEvents(options = {}) {
@@ -311,34 +296,14 @@ class RingCamera extends subscribed_1.Subscribed {
311
296
  get snapshotsAreBlocked() {
312
297
  return this.data.settings.motion_detection_enabled === false;
313
298
  }
314
- getSnapshotTimestamp() {
315
- return __awaiter(this, void 0, void 0, function* () {
316
- const { timestamps, responseTimestamp } = yield this.restClient.request({
317
- url: (0, rest_client_1.clientApi)('snapshots/timestamps'),
318
- method: 'POST',
319
- json: {
320
- doorbot_ids: [this.id],
321
- },
322
- }), deviceTimestamp = timestamps[0], timestamp = deviceTimestamp ? deviceTimestamp.timestamp : 0, timestampAge = Math.abs(responseTimestamp - timestamp);
323
- this.lastSnapshotTimestampLocal = timestamp ? Date.now() - timestampAge : 0;
324
- return {
325
- timestamp,
326
- inLifeTime: this.isTimestampInLifeTime(timestampAge),
327
- };
328
- });
329
- }
330
299
  get snapshotLifeTime() {
331
300
  return this.avoidSnapshotBatteryDrain && this.operatingOnBattery
332
301
  ? 600 * 1000 // battery cams only refresh timestamp every 10 minutes
333
- : 10 * 1000; // snapshot updates will be forced. Limit to 10 lifetime
302
+ : 10 * 1000; // snapshot updates will be forced. Limit to 10s lifetime
334
303
  }
335
304
  get currentTimestampAge() {
336
305
  return Date.now() - this.lastSnapshotTimestampLocal;
337
306
  }
338
- get currentTimestampExpiresIn() {
339
- // Gets 0 if stale snapshot is used because snapshot timestamp refused to update (recording in progress on battery cam)
340
- return Math.max(this.lastSnapshotTimestampLocal - Date.now() + this.snapshotLifeTime, 0);
341
- }
342
307
  get hasSnapshotWithinLifetime() {
343
308
  return this.isTimestampInLifeTime(this.currentTimestampAge);
344
309
  }
@@ -346,131 +311,86 @@ class RingCamera extends subscribed_1.Subscribed {
346
311
  if (this.snapshotsAreBlocked) {
347
312
  throw new Error(`Motion detection is disabled for ${this.name}, which prevents snapshots from this camera. This can be caused by Modes settings or by turning off the Record Motion setting.`);
348
313
  }
314
+ if (this.isOffline) {
315
+ throw new Error(`Cannot fetch snapshot for ${this.name} because it is offline`);
316
+ }
349
317
  }
350
- requestSnapshotUpdate() {
351
- return this.restClient.request({
352
- method: 'PUT',
353
- url: (0, rest_client_1.clientApi)('snapshots/update_all'),
354
- json: {
355
- doorbot_ids: [this.id],
356
- refresh: true,
357
- },
358
- });
359
- }
360
- refreshSnapshot() {
361
- return __awaiter(this, void 0, void 0, function* () {
362
- if (this.hasSnapshotWithinLifetime) {
363
- (0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
364
- return true;
365
- }
366
- this.checkIfSnapshotsAreBlocked();
367
- if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
368
- // tell the camera to update snapshot immediately.
369
- // avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
370
- yield this.requestSnapshotUpdate();
371
- }
372
- for (let i = 0; i < maxSnapshotRefreshAttempts; i++) {
373
- this.checkIfSnapshotsAreBlocked();
374
- const { timestamp, inLifeTime } = yield this.getSnapshotTimestamp();
375
- if (!timestamp && this.isOffline) {
376
- throw new Error(`No snapshot available and device ${this.name} is offline`);
377
- }
378
- if (inLifeTime) {
379
- return false;
380
- }
381
- yield (0, util_1.delay)(snapshotRefreshDelay);
382
- }
383
- const extraMessageForBatteryCam = this.operatingOnBattery
384
- ? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
385
- : '';
386
- throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshAttempts} attempts${extraMessageForBatteryCam}`);
387
- });
318
+ shouldUseExistingSnapshotPromise() {
319
+ if (this.fetchingSnapshot) {
320
+ return true;
321
+ }
322
+ if (this.hasSnapshotWithinLifetime) {
323
+ (0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
324
+ return true;
325
+ }
326
+ if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
327
+ // tell the camera to update snapshot immediately.
328
+ // avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
329
+ return false;
330
+ }
388
331
  }
389
332
  getSnapshot() {
390
333
  return __awaiter(this, void 0, void 0, function* () {
391
- this.refreshSnapshotInProgress =
392
- this.refreshSnapshotInProgress ||
393
- this.refreshSnapshot().catch((e) => {
394
- (0, util_1.logError)(e.message);
395
- throw e;
396
- });
397
- try {
398
- const useLastSnapshot = yield this.refreshSnapshotInProgress;
399
- this.refreshSnapshotInProgress = undefined;
400
- if (useLastSnapshot && this.lastSnapshotPromise) {
401
- return this.lastSnapshotPromise;
402
- }
334
+ if (this.lastSnapshotPromise && this.shouldUseExistingSnapshotPromise()) {
335
+ return this.lastSnapshotPromise;
403
336
  }
404
- catch (e) {
405
- this.refreshSnapshotInProgress = undefined;
406
- throw e;
407
- }
408
- this.lastSnapshotPromise = this.restClient.request({
409
- url: (0, rest_client_1.clientApi)(`snapshots/image/${this.id}`),
410
- responseType: 'buffer',
411
- });
337
+ this.checkIfSnapshotsAreBlocked();
338
+ this.lastSnapshotPromise = Promise.race([
339
+ this.getNextSnapshot({
340
+ afterMs: this.lastSnapshotTimestamp,
341
+ force: true,
342
+ }),
343
+ (0, util_1.delay)(maxSnapshotRefreshSeconds * 1000).then(() => {
344
+ const extraMessageForBatteryCam = this.operatingOnBattery
345
+ ? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
346
+ : '';
347
+ throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshSeconds} seconds${extraMessageForBatteryCam}`);
348
+ }),
349
+ ]);
412
350
  try {
413
351
  yield this.lastSnapshotPromise;
414
352
  }
415
- catch (_) {
353
+ catch (e) {
416
354
  // snapshot request failed, don't use it again
417
355
  this.lastSnapshotPromise = undefined;
356
+ throw e;
418
357
  }
358
+ this.fetchingSnapshot = false;
419
359
  return this.lastSnapshotPromise;
420
360
  });
421
361
  }
422
- getSipOptions() {
423
- return __awaiter(this, void 0, void 0, function* () {
424
- const activeDings = this.onActiveDings.getValue(), existingDing = activeDings
425
- .filter((ding) => !this.expiredDingIds.includes(ding.id_str))
426
- .slice()
427
- .reverse()[0], ding = existingDing || (yield this.getSipConnectionDetails());
428
- return {
429
- to: ding.sip_to,
430
- from: ding.sip_from,
431
- dingId: ding.id_str,
432
- localIp: yield (0, camera_utils_1.getDefaultIpAddress)(),
433
- };
434
- });
435
- }
436
- getUpdatedSipOptions(expiredDingId) {
437
- // Got a 480 from sip session, which means it's no longer active
438
- this.expiredDingIds.push(expiredDingId);
439
- return this.getSipOptions();
440
- }
441
- createSipSession(options = {}) {
362
+ getNextSnapshot({ afterMs, maxWaitMs, force, }) {
442
363
  return __awaiter(this, void 0, void 0, function* () {
443
- const audioSplitter = new camera_utils_1.RtpSplitter(), audioRtcpSplitter = new camera_utils_1.RtpSplitter(), videoSplitter = new camera_utils_1.RtpSplitter(), videoRtcpSplitter = new camera_utils_1.RtpSplitter(), [sipOptions, ffmpegIsInstalled, audioPort, audioRtcpPort, videoPort, videoRtcpPort, [tlsPort],] = yield Promise.all([
444
- this.getSipOptions(),
445
- options.skipFfmpegCheck ? Promise.resolve(true) : (0, camera_utils_1.isFfmpegInstalled)(),
446
- audioSplitter.portPromise,
447
- audioRtcpSplitter.portPromise,
448
- videoSplitter.portPromise,
449
- videoRtcpSplitter.portPromise,
450
- (0, camera_utils_1.reservePorts)({ type: 'tcp' }),
451
- ]), rtpOptions = {
452
- audio: Object.assign({ port: audioPort, rtcpPort: audioRtcpPort }, (options.audio || (0, camera_utils_1.generateSrtpOptions)())),
453
- video: Object.assign({ port: videoPort, rtcpPort: videoRtcpPort }, (options.video || (0, camera_utils_1.generateSrtpOptions)())),
454
- };
455
- if (!ffmpegIsInstalled) {
456
- throw new Error('Ffmpeg is not installed. See https://github.com/dgreif/ring/wiki/FFmpeg for directions.');
457
- }
458
- return new sip_session_1.SipSession(sipOptions, rtpOptions, audioSplitter, audioRtcpSplitter, videoSplitter, videoRtcpSplitter, tlsPort, this);
364
+ const response = yield this.restClient.request({
365
+ url: `https://app-snaps.ring.com/snapshots/next/${this.id}?extras=force`,
366
+ responseType: 'buffer',
367
+ searchParams: {
368
+ 'after-ms': afterMs,
369
+ 'max-wait-ms': maxWaitMs,
370
+ extras: force ? 'force' : undefined,
371
+ },
372
+ headers: {
373
+ accept: 'image/jpeg',
374
+ },
375
+ }), { responseTimestamp, timeMillis } = response, timestampAge = Math.abs(responseTimestamp - timeMillis);
376
+ this.lastSnapshotTimestamp = timeMillis;
377
+ this.lastSnapshotTimestampLocal = Date.now() - timestampAge;
378
+ return response;
459
379
  });
460
380
  }
461
381
  recordToFile(outputPath, duration = 30) {
462
382
  return __awaiter(this, void 0, void 0, function* () {
463
- const sipSession = yield this.streamVideo({
383
+ const liveCall = yield this.streamVideo({
464
384
  output: ['-t', duration.toString(), outputPath],
465
385
  });
466
- yield (0, rxjs_1.firstValueFrom)(sipSession.onCallEnded);
386
+ yield (0, rxjs_1.firstValueFrom)(liveCall.onCallEnded);
467
387
  });
468
388
  }
469
389
  streamVideo(ffmpegOptions) {
470
390
  return __awaiter(this, void 0, void 0, function* () {
471
- const sipSession = yield this.createSipSession();
472
- yield sipSession.start(ffmpegOptions);
473
- return sipSession;
391
+ const liveCall = yield this.startLiveCall();
392
+ yield liveCall.startTranscoding(ffmpegOptions);
393
+ return liveCall;
474
394
  });
475
395
  }
476
396
  subscribeToDingEvents() {
@@ -502,5 +422,3 @@ class RingCamera extends subscribed_1.Subscribed {
502
422
  }
503
423
  }
504
424
  exports.RingCamera = RingCamera;
505
- // SOMEDAY: extract image from video file?
506
- // ffmpeg -i input.mp4 -r 1 -f image2 image-%2d.png
@@ -5,7 +5,7 @@ export declare class RingChime {
5
5
  private initialData;
6
6
  private restClient;
7
7
  id: number;
8
- deviceType: "chime" | "chime_pro" | "chime_v2" | "chime_pro_v2";
8
+ deviceType: import("./ring-types").ChimeKind;
9
9
  model: string;
10
10
  onData: BehaviorSubject<ChimeData>;
11
11
  onRequestUpdate: Subject<unknown>;
@@ -23,11 +23,11 @@ class RingChime {
23
23
  constructor(initialData, restClient) {
24
24
  this.initialData = initialData;
25
25
  this.restClient = restClient;
26
+ this.onRequestUpdate = new rxjs_1.Subject();
26
27
  this.id = this.initialData.id;
27
28
  this.deviceType = this.initialData.kind;
28
29
  this.model = ring_types_1.ChimeModel[this.deviceType] || 'Chime';
29
30
  this.onData = new rxjs_1.BehaviorSubject(this.initialData);
30
- this.onRequestUpdate = new rxjs_1.Subject();
31
31
  }
32
32
  updateData(update) {
33
33
  this.onData.next(update);
@@ -10,6 +10,7 @@ export declare enum RingDeviceType {
10
10
  TemperatureSensor = "sensor.temperature",
11
11
  WaterSensor = "sensor.water",
12
12
  TiltSensor = "sensor.tilt",
13
+ GlassbreakSensor = "sensor.glassbreak",
13
14
  RangeExtender = "range-extender.zwave",
14
15
  ZigbeeAdapter = "adapter.zigbee",
15
16
  AccessCodeVault = "access-code.vault",
@@ -67,6 +68,7 @@ export declare enum RingCameraKind {
67
68
  doorbell_portal = "doorbell_portal",
68
69
  doorbell_scallop = "doorbell_scallop",
69
70
  doorbell_scallop_lite = "doorbell_scallop_lite",
71
+ doorbell_graham_cracker = "doorbell_graham_cracker",
70
72
  lpd_v1 = "lpd_v1",
71
73
  lpd_v2 = "lpd_v2",
72
74
  jbox_v1 = "jbox_v1",
@@ -80,6 +82,7 @@ export declare enum RingCameraKind {
80
82
  stickup_cam_v4 = "stickup_cam_v4",
81
83
  floodlight_v1 = "floodlight_v1",
82
84
  floodlight_v2 = "floodlight_v2",
85
+ floodlight_pro = "floodlight_pro",
83
86
  cocoa_camera = "cocoa_camera",
84
87
  cocoa_doorbell = "cocoa_doorbell",
85
88
  stickup_cam_mini = "stickup_cam_mini"
@@ -214,7 +217,7 @@ export interface BeamBridge {
214
217
  role: string;
215
218
  updated_at: string;
216
219
  }
217
- declare type ChimeKind = 'chime' | 'chime_pro' | 'chime_v2' | 'chime_pro_v2';
220
+ export declare type ChimeKind = 'chime' | 'chime_pro' | 'chime_v2' | 'chime_pro_v2';
218
221
  export declare const ChimeModel: {
219
222
  readonly [P in ChimeKind]: string;
220
223
  };
@@ -686,9 +689,20 @@ export interface ActiveDing {
686
689
  sip_token: string;
687
690
  sip_ding_id: string;
688
691
  }
689
- export interface SnapshotTimestamp {
690
- timestamp: number;
691
- doorbot_id: number;
692
+ export interface LiveCallResponse {
693
+ ding_id: string;
694
+ ding_kind: DingKind;
695
+ device_id: number;
696
+ device_kind: RingCameraKind;
697
+ device_description: string;
698
+ state: DingState;
699
+ protocol: 'ring_media_server' | unknown;
700
+ now: number;
701
+ video_jitter_buffer_ms: number;
702
+ audio_jitter_buffer_ms: number;
703
+ data: {
704
+ session_id: string;
705
+ };
692
706
  }
693
707
  export interface AuthTokenResponse {
694
708
  access_token: string;
@@ -14,6 +14,7 @@ var RingDeviceType;
14
14
  RingDeviceType["TemperatureSensor"] = "sensor.temperature";
15
15
  RingDeviceType["WaterSensor"] = "sensor.water";
16
16
  RingDeviceType["TiltSensor"] = "sensor.tilt";
17
+ RingDeviceType["GlassbreakSensor"] = "sensor.glassbreak";
17
18
  RingDeviceType["RangeExtender"] = "range-extender.zwave";
18
19
  RingDeviceType["ZigbeeAdapter"] = "adapter.zigbee";
19
20
  RingDeviceType["AccessCodeVault"] = "access-code.vault";
@@ -75,6 +76,7 @@ var RingCameraKind;
75
76
  RingCameraKind["doorbell_portal"] = "doorbell_portal";
76
77
  RingCameraKind["doorbell_scallop"] = "doorbell_scallop";
77
78
  RingCameraKind["doorbell_scallop_lite"] = "doorbell_scallop_lite";
79
+ RingCameraKind["doorbell_graham_cracker"] = "doorbell_graham_cracker";
78
80
  RingCameraKind["lpd_v1"] = "lpd_v1";
79
81
  RingCameraKind["lpd_v2"] = "lpd_v2";
80
82
  RingCameraKind["jbox_v1"] = "jbox_v1";
@@ -88,12 +90,13 @@ var RingCameraKind;
88
90
  RingCameraKind["stickup_cam_v4"] = "stickup_cam_v4";
89
91
  RingCameraKind["floodlight_v1"] = "floodlight_v1";
90
92
  RingCameraKind["floodlight_v2"] = "floodlight_v2";
93
+ RingCameraKind["floodlight_pro"] = "floodlight_pro";
91
94
  RingCameraKind["cocoa_camera"] = "cocoa_camera";
92
95
  RingCameraKind["cocoa_doorbell"] = "cocoa_doorbell";
93
96
  RingCameraKind["stickup_cam_mini"] = "stickup_cam_mini";
94
97
  })(RingCameraKind = exports.RingCameraKind || (exports.RingCameraKind = {}));
95
98
  // RegExp taken from ring.com app
96
- const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight))/);
99
+ const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight|doorbell_graham_cracker))/);
97
100
  function isBatteryCameraKind(kind) {
98
101
  return !kind.match(isWiredCameraRegExp);
99
102
  }
@@ -107,6 +110,7 @@ exports.RingCameraModel = {
107
110
  doorbell_portal: 'Door View Cam',
108
111
  doorbell_scallop: 'Doorbell 3 Plus',
109
112
  doorbell_scallop_lite: 'Doorbell 3',
113
+ doorbell_graham_cracker: 'Doorbell Wired',
110
114
  lpd_v1: 'Doorbell Pro',
111
115
  lpd_v2: 'Doorbell Pro',
112
116
  jbox_v1: 'Doorbell Elite',
@@ -120,6 +124,7 @@ exports.RingCameraModel = {
120
124
  stickup_cam_v4: 'Spotlight Cam',
121
125
  floodlight_v1: 'Floodlight Cam',
122
126
  floodlight_v2: 'Floodlight Cam',
127
+ floodlight_pro: 'Floodlight Pro',
123
128
  cocoa_camera: 'Stick Up Cam',
124
129
  cocoa_doorbell: 'Doorbell Gen 2',
125
130
  stickup_cam_mini: 'Indoor Cam',
@@ -1,29 +1,26 @@
1
- /// <reference types="node" />
2
- import { RtpSplitter, SrtpOptions } from '@homebridge/camera-utils';
3
- export interface RtpStreamOptions extends SrtpOptions {
1
+ import { DingKind } from './ring-types';
2
+ export interface LiveCallSession {
3
+ alexa_port: 18443;
4
+ app_session_token: string;
5
+ availability_zone: 'availability-zone';
6
+ custom_timer: {
7
+ max_sec: number;
8
+ };
9
+ ding_id: string;
10
+ ding_kind: DingKind;
11
+ doorbot_id: number;
12
+ exp: number;
13
+ ip: string;
4
14
  port: number;
5
- rtcpPort: number;
15
+ private_ip: string;
16
+ rms_fqdn: string;
17
+ rms_version: string;
18
+ rsp_port: number;
19
+ rtsp_port: number;
20
+ session_id: string;
21
+ sip_port: number;
22
+ webrtc_port: number;
23
+ webrtc_url: string;
24
+ wwr_port: number;
6
25
  }
7
- export interface RtpOptions {
8
- audio: RtpStreamOptions;
9
- video: RtpStreamOptions;
10
- }
11
- export interface RtpStreamDescription extends RtpStreamOptions {
12
- ssrc?: number;
13
- iceUFrag?: string;
14
- icePwd?: string;
15
- }
16
- export interface RtpDescription {
17
- address: string;
18
- audio: RtpStreamDescription;
19
- video: RtpStreamDescription;
20
- }
21
- export declare function isStunMessage(message: Buffer): boolean;
22
- export declare function sendStunBindingRequest({ rtpDescription, rtpSplitter, rtcpSplitter, localUfrag, type, }: {
23
- rtpSplitter: RtpSplitter;
24
- rtcpSplitter: RtpSplitter;
25
- rtpDescription: RtpDescription;
26
- localUfrag?: string;
27
- type: 'video' | 'audio';
28
- }): void;
29
- export declare function createStunResponder(rtpSplitter: RtpSplitter): void;
26
+ export declare function parseLiveCallSession(sessionId: string): LiveCallSession;
@@ -1,56 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createStunResponder = exports.sendStunBindingRequest = exports.isStunMessage = void 0;
4
- const util_1 = require("./util");
5
- const stun = require('stun');
6
- const stunMagicCookie = 0x2112a442; // https://tools.ietf.org/html/rfc5389#section-6
7
- function isStunMessage(message) {
8
- return message.length > 8 && message.readInt32BE(4) === stunMagicCookie;
3
+ exports.parseLiveCallSession = void 0;
4
+ function parseLiveCallSession(sessionId) {
5
+ const encodedSession = sessionId.split('.')[1], buff = Buffer.from(encodedSession, 'base64'), text = buff.toString('ascii');
6
+ return JSON.parse(text);
9
7
  }
10
- exports.isStunMessage = isStunMessage;
11
- function sendStunBindingRequest({ rtpDescription, rtpSplitter, rtcpSplitter, localUfrag, type, }) {
12
- const message = stun.createMessage(1), remoteDescription = rtpDescription[type], { address } = rtpDescription, { iceUFrag, icePwd, port, rtcpPort } = remoteDescription;
13
- if (iceUFrag && icePwd && localUfrag) {
14
- // Full ICE supported. Send as formal stun request
15
- message.addUsername(iceUFrag + ':' + localUfrag);
16
- message.addMessageIntegrity(icePwd);
17
- stun
18
- .request(`${address}:${port}`, {
19
- socket: rtpSplitter.socket,
20
- message,
21
- })
22
- .then(() => (0, util_1.logDebug)(`${type} stun complete`))
23
- .catch((e) => {
24
- (0, util_1.logError)(`${type} stun error`);
25
- (0, util_1.logError)(e);
26
- });
27
- }
28
- else {
29
- // ICE not supported. Fire and forget the stun request for RTP and RTCP
30
- const encodedMessage = stun.encode(message);
31
- rtpSplitter.send(encodedMessage, { address, port }).catch(util_1.logError);
32
- rtcpSplitter
33
- .send(encodedMessage, { address, port: rtcpPort })
34
- .catch(util_1.logError);
35
- }
36
- }
37
- exports.sendStunBindingRequest = sendStunBindingRequest;
38
- function createStunResponder(rtpSplitter) {
39
- return rtpSplitter.addMessageHandler(({ message, info }) => {
40
- if (!isStunMessage(message)) {
41
- return null;
42
- }
43
- try {
44
- const decodedMessage = stun.decode(message), response = stun.createMessage(stun.constants.STUN_BINDING_RESPONSE, decodedMessage.transactionId);
45
- response.addXorAddress(info.address, info.port);
46
- rtpSplitter.send(stun.encode(response), info).catch(util_1.logError);
47
- }
48
- catch (e) {
49
- (0, util_1.logDebug)('Failed to Decode STUN Message');
50
- (0, util_1.logDebug)(message.toString('hex'));
51
- (0, util_1.logDebug)(e);
52
- }
53
- return null;
54
- });
55
- }
56
- exports.createStunResponder = createStunResponder;
8
+ exports.parseLiveCallSession = parseLiveCallSession;
package/lib/api/util.js CHANGED
@@ -8,14 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  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;
13
- const debug = require("debug");
16
+ const debug_1 = __importDefault(require("debug"));
14
17
  const colors_1 = require("colors");
15
18
  const readline_1 = require("readline");
16
19
  const uuid_1 = require("uuid");
17
20
  const systeminformation_1 = require("systeminformation");
18
- const debugLogger = debug('ring'), uuidNamespace = 'e53ffdc0-e91d-4ce1-bec2-df939d94739c';
21
+ const debugLogger = (0, debug_1.default)('ring'), uuidNamespace = 'e53ffdc0-e91d-4ce1-bec2-df939d94739c';
19
22
  let logger = {
20
23
  logInfo(message) {
21
24
  debugLogger(message);