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.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/lib/api/api.js +3 -1
- package/lib/api/index.d.ts +0 -1
- package/lib/api/index.js +0 -1
- package/lib/api/live-call.d.ts +37 -0
- package/lib/api/live-call.js +182 -0
- package/lib/api/peer-connection.d.ts +17 -0
- package/lib/api/peer-connection.js +96 -0
- package/lib/api/rest-client.d.ts +3 -0
- package/lib/api/rest-client.js +18 -6
- package/lib/api/ring-camera.d.ts +13 -22
- package/lib/api/ring-camera.js +92 -174
- package/lib/api/ring-chime.d.ts +1 -1
- package/lib/api/ring-chime.js +1 -1
- package/lib/api/ring-types.d.ts +18 -4
- package/lib/api/ring-types.js +6 -1
- package/lib/api/rtp-utils.d.ts +24 -27
- package/lib/api/rtp-utils.js +5 -53
- package/lib/api/util.js +5 -2
- package/package.json +38 -34
- package/lib/api/sip-call.d.ts +0 -76
- package/lib/api/sip-call.js +0 -242
- package/lib/api/sip-session.d.ts +0 -37
- package/lib/api/sip-session.js +0 -212
- package/lib/api/sip.d.ts +0 -86
- package/lib/api/sip.js +0 -1313
package/lib/api/ring-camera.js
CHANGED
|
@@ -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
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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.
|
|
392
|
-
this.
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
this.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
383
|
+
const liveCall = yield this.streamVideo({
|
|
464
384
|
output: ['-t', duration.toString(), outputPath],
|
|
465
385
|
});
|
|
466
|
-
yield (0, rxjs_1.firstValueFrom)(
|
|
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
|
|
472
|
-
yield
|
|
473
|
-
return
|
|
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
|
package/lib/api/ring-chime.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare class RingChime {
|
|
|
5
5
|
private initialData;
|
|
6
6
|
private restClient;
|
|
7
7
|
id: number;
|
|
8
|
-
deviceType: "
|
|
8
|
+
deviceType: import("./ring-types").ChimeKind;
|
|
9
9
|
model: string;
|
|
10
10
|
onData: BehaviorSubject<ChimeData>;
|
|
11
11
|
onRequestUpdate: Subject<unknown>;
|
package/lib/api/ring-chime.js
CHANGED
|
@@ -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);
|
package/lib/api/ring-types.d.ts
CHANGED
|
@@ -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
|
|
690
|
-
|
|
691
|
-
|
|
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;
|
package/lib/api/ring-types.js
CHANGED
|
@@ -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',
|
package/lib/api/rtp-utils.d.ts
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
package/lib/api/rtp-utils.js
CHANGED
|
@@ -1,56 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
const
|
|
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.
|
|
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
|
|
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 =
|
|
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);
|