ring-client-api 13.1.0 → 13.2.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.
- package/lib/api.js +4 -0
- package/lib/location.js +45 -34
- package/lib/rest-client.js +32 -16
- package/lib/ring-camera.js +29 -16
- package/lib/ring-chime.js +7 -1
- package/lib/ring-device.js +9 -0
- package/lib/ring-intercom.js +9 -3
- package/lib/streaming/peer-connection.js +12 -9
- package/lib/streaming/simple-webrtc-session.js +3 -1
- package/lib/streaming/streaming-messages.d.ts +12 -1
- package/lib/streaming/streaming-session.js +11 -9
- package/lib/streaming/webrtc-connection.js +20 -10
- package/lib/subscribed.js +1 -3
- package/package.json +17 -16
package/lib/api.js
CHANGED
|
@@ -18,6 +18,9 @@ const push_receiver_1 = __importDefault(require("@eneris/push-receiver"));
|
|
|
18
18
|
const ring_intercom_1 = require("./ring-intercom");
|
|
19
19
|
const json_bigint_1 = __importDefault(require("json-bigint"));
|
|
20
20
|
class RingApi extends subscribed_1.Subscribed {
|
|
21
|
+
options;
|
|
22
|
+
restClient;
|
|
23
|
+
onRefreshTokenUpdated;
|
|
21
24
|
constructor(options) {
|
|
22
25
|
super();
|
|
23
26
|
this.options = options;
|
|
@@ -273,6 +276,7 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
273
276
|
});
|
|
274
277
|
return locations;
|
|
275
278
|
}
|
|
279
|
+
locationsPromise;
|
|
276
280
|
getLocations() {
|
|
277
281
|
if (!this.locationsPromise) {
|
|
278
282
|
this.locationsPromise = this.fetchAndBuildLocations();
|
package/lib/location.js
CHANGED
|
@@ -15,6 +15,51 @@ function flattenDeviceData(data) {
|
|
|
15
15
|
return Object.assign({}, data.general && data.general.v2, data.device && data.device.v1);
|
|
16
16
|
}
|
|
17
17
|
class Location extends subscribed_1.Subscribed {
|
|
18
|
+
locationDetails;
|
|
19
|
+
cameras;
|
|
20
|
+
chimes;
|
|
21
|
+
intercoms;
|
|
22
|
+
options;
|
|
23
|
+
restClient;
|
|
24
|
+
seq = 1;
|
|
25
|
+
onMessage = new rxjs_1.Subject();
|
|
26
|
+
onDataUpdate = new rxjs_1.Subject();
|
|
27
|
+
onDeviceDataUpdate = this.onDataUpdate.pipe((0, operators_1.filter)((message) => {
|
|
28
|
+
return message.datatype === 'DeviceInfoDocType' && Boolean(message.body);
|
|
29
|
+
}), (0, operators_1.concatMap)((message) => message.body), (0, operators_1.map)(flattenDeviceData));
|
|
30
|
+
onDeviceList = this.onMessage.pipe((0, operators_1.filter)((m) => m.msg === deviceListMessageType));
|
|
31
|
+
onDevices = this.onDeviceList.pipe((0, operators_1.scan)((devices, { body: deviceList, src }) => {
|
|
32
|
+
if (!deviceList) {
|
|
33
|
+
return devices;
|
|
34
|
+
}
|
|
35
|
+
if (!this.receivedAssetDeviceLists.includes(src)) {
|
|
36
|
+
this.receivedAssetDeviceLists.push(src);
|
|
37
|
+
}
|
|
38
|
+
return deviceList.reduce((updatedDevices, data) => {
|
|
39
|
+
const flatData = flattenDeviceData(data), existingDevice = updatedDevices.find((x) => x.zid === flatData.zid);
|
|
40
|
+
if (existingDevice) {
|
|
41
|
+
existingDevice.updateData(flatData);
|
|
42
|
+
return updatedDevices;
|
|
43
|
+
}
|
|
44
|
+
return [...updatedDevices, new ring_device_1.RingDevice(flatData, this, src)];
|
|
45
|
+
}, devices);
|
|
46
|
+
}, []), (0, operators_1.distinctUntilChanged)((a, b) => a.length === b.length), (0, operators_1.filter)(() => {
|
|
47
|
+
return Boolean(this.assets &&
|
|
48
|
+
this.assets.every((asset) => this.receivedAssetDeviceLists.includes(asset.uuid)));
|
|
49
|
+
}), (0, operators_1.shareReplay)(1));
|
|
50
|
+
onSessionInfo = this.onDataUpdate.pipe((0, operators_1.filter)((m) => m.msg === 'SessionInfo'), (0, operators_1.map)((m) => m.body));
|
|
51
|
+
onConnected = new rxjs_1.BehaviorSubject(false);
|
|
52
|
+
onLocationMode = new rxjs_1.ReplaySubject(1);
|
|
53
|
+
onLocationModeRequested = new rxjs_1.Subject();
|
|
54
|
+
reconnecting = false;
|
|
55
|
+
disconnected = false;
|
|
56
|
+
connectionPromise;
|
|
57
|
+
securityPanel;
|
|
58
|
+
assets;
|
|
59
|
+
receivedAssetDeviceLists = [];
|
|
60
|
+
offlineAssets = [];
|
|
61
|
+
hasHubs;
|
|
62
|
+
hasAlarmBaseStation;
|
|
18
63
|
constructor(locationDetails, cameras, chimes, intercoms, options, restClient) {
|
|
19
64
|
super();
|
|
20
65
|
this.locationDetails = locationDetails;
|
|
@@ -23,40 +68,6 @@ class Location extends subscribed_1.Subscribed {
|
|
|
23
68
|
this.intercoms = intercoms;
|
|
24
69
|
this.options = options;
|
|
25
70
|
this.restClient = restClient;
|
|
26
|
-
this.seq = 1;
|
|
27
|
-
this.onMessage = new rxjs_1.Subject();
|
|
28
|
-
this.onDataUpdate = new rxjs_1.Subject();
|
|
29
|
-
this.onDeviceDataUpdate = this.onDataUpdate.pipe((0, operators_1.filter)((message) => {
|
|
30
|
-
return message.datatype === 'DeviceInfoDocType' && Boolean(message.body);
|
|
31
|
-
}), (0, operators_1.concatMap)((message) => message.body), (0, operators_1.map)(flattenDeviceData));
|
|
32
|
-
this.onDeviceList = this.onMessage.pipe((0, operators_1.filter)((m) => m.msg === deviceListMessageType));
|
|
33
|
-
this.onDevices = this.onDeviceList.pipe((0, operators_1.scan)((devices, { body: deviceList, src }) => {
|
|
34
|
-
if (!deviceList) {
|
|
35
|
-
return devices;
|
|
36
|
-
}
|
|
37
|
-
if (!this.receivedAssetDeviceLists.includes(src)) {
|
|
38
|
-
this.receivedAssetDeviceLists.push(src);
|
|
39
|
-
}
|
|
40
|
-
return deviceList.reduce((updatedDevices, data) => {
|
|
41
|
-
const flatData = flattenDeviceData(data), existingDevice = updatedDevices.find((x) => x.zid === flatData.zid);
|
|
42
|
-
if (existingDevice) {
|
|
43
|
-
existingDevice.updateData(flatData);
|
|
44
|
-
return updatedDevices;
|
|
45
|
-
}
|
|
46
|
-
return [...updatedDevices, new ring_device_1.RingDevice(flatData, this, src)];
|
|
47
|
-
}, devices);
|
|
48
|
-
}, []), (0, operators_1.distinctUntilChanged)((a, b) => a.length === b.length), (0, operators_1.filter)(() => {
|
|
49
|
-
return Boolean(this.assets &&
|
|
50
|
-
this.assets.every((asset) => this.receivedAssetDeviceLists.includes(asset.uuid)));
|
|
51
|
-
}), (0, operators_1.shareReplay)(1));
|
|
52
|
-
this.onSessionInfo = this.onDataUpdate.pipe((0, operators_1.filter)((m) => m.msg === 'SessionInfo'), (0, operators_1.map)((m) => m.body));
|
|
53
|
-
this.onConnected = new rxjs_1.BehaviorSubject(false);
|
|
54
|
-
this.onLocationMode = new rxjs_1.ReplaySubject(1);
|
|
55
|
-
this.onLocationModeRequested = new rxjs_1.Subject();
|
|
56
|
-
this.reconnecting = false;
|
|
57
|
-
this.disconnected = false;
|
|
58
|
-
this.receivedAssetDeviceLists = [];
|
|
59
|
-
this.offlineAssets = [];
|
|
60
71
|
this.hasHubs = this.options.hasHubs;
|
|
61
72
|
this.hasAlarmBaseStation = this.options.hasAlarmBaseStation;
|
|
62
73
|
this.addSubscriptions(
|
package/lib/rest-client.js
CHANGED
|
@@ -55,7 +55,10 @@ async function responseToError(response) {
|
|
|
55
55
|
}
|
|
56
56
|
return error;
|
|
57
57
|
}
|
|
58
|
-
async function requestWithRetry(requestOptions) {
|
|
58
|
+
async function requestWithRetry(requestOptions, retryCount = 0) {
|
|
59
|
+
if (typeof fetch !== 'function') {
|
|
60
|
+
throw new Error(`Your current NodeJS version (${process.version}) is too old to support this plugin. Please upgrade to the latest LTS version of NodeJS.`);
|
|
61
|
+
}
|
|
59
62
|
try {
|
|
60
63
|
if (requestOptions.json || requestOptions.responseType === 'json') {
|
|
61
64
|
requestOptions.headers = {
|
|
@@ -110,13 +113,18 @@ async function requestWithRetry(requestOptions) {
|
|
|
110
113
|
}
|
|
111
114
|
catch (e) {
|
|
112
115
|
if (!e.response && !requestOptions.allowNoResponse) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (retryCount > 0) {
|
|
117
|
+
let detailedError = `Error: ${e.message}`;
|
|
118
|
+
detailedError += e.cause?.message ? `, Cause: ${e.cause.message}` : '';
|
|
119
|
+
detailedError += e.cause?.code ? `, Code: ${e.cause.code}` : '';
|
|
120
|
+
(0, util_1.logError)(`Retry #${retryCount} failed to reach Ring server at ${requestOptions.url}. ${detailedError}. Trying again in 5 seconds...`);
|
|
121
|
+
if (e.message.includes('NGHTTP2_ENHANCE_YOUR_CALM')) {
|
|
122
|
+
(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`);
|
|
123
|
+
}
|
|
124
|
+
(0, util_1.logDebug)(e);
|
|
116
125
|
}
|
|
117
|
-
(0, util_1.logDebug)(e);
|
|
118
126
|
await (0, util_1.delay)(5000);
|
|
119
|
-
return requestWithRetry(requestOptions);
|
|
127
|
+
return requestWithRetry(requestOptions, retryCount + 1);
|
|
120
128
|
}
|
|
121
129
|
throw e;
|
|
122
130
|
}
|
|
@@ -138,6 +146,12 @@ function parseAuthConfig(rawRefreshToken) {
|
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
class RingRestClient {
|
|
149
|
+
authOptions;
|
|
150
|
+
refreshToken;
|
|
151
|
+
authConfig;
|
|
152
|
+
hardwareIdPromise;
|
|
153
|
+
_authPromise;
|
|
154
|
+
timeouts = [];
|
|
141
155
|
clearPreviousAuth() {
|
|
142
156
|
this._authPromise = undefined;
|
|
143
157
|
}
|
|
@@ -161,21 +175,22 @@ class RingRestClient {
|
|
|
161
175
|
}
|
|
162
176
|
return this._authPromise;
|
|
163
177
|
}
|
|
178
|
+
sessionPromise = undefined;
|
|
179
|
+
using2fa = false;
|
|
180
|
+
promptFor2fa;
|
|
181
|
+
onRefreshTokenUpdated = new rxjs_1.ReplaySubject(1);
|
|
182
|
+
onSession = new rxjs_1.ReplaySubject(1);
|
|
183
|
+
baseSessionMetadata;
|
|
164
184
|
constructor(authOptions) {
|
|
165
185
|
this.authOptions = authOptions;
|
|
166
|
-
this.refreshToken =
|
|
167
|
-
?
|
|
168
|
-
: undefined;
|
|
186
|
+
this.refreshToken =
|
|
187
|
+
'refreshToken' in authOptions ? authOptions.refreshToken : undefined;
|
|
169
188
|
this.authConfig = parseAuthConfig(this.refreshToken);
|
|
170
|
-
this.hardwareIdPromise =
|
|
171
|
-
|
|
172
|
-
this.sessionPromise = undefined;
|
|
173
|
-
this.using2fa = false;
|
|
174
|
-
this.onRefreshTokenUpdated = new rxjs_1.ReplaySubject(1);
|
|
175
|
-
this.onSession = new rxjs_1.ReplaySubject(1);
|
|
189
|
+
this.hardwareIdPromise =
|
|
190
|
+
this.authConfig?.hid || (0, util_1.getHardwareId)(authOptions.systemId);
|
|
176
191
|
this.baseSessionMetadata = {
|
|
177
192
|
api_version: apiVersion,
|
|
178
|
-
device_model:
|
|
193
|
+
device_model: authOptions.controlCenterDisplayName ?? 'ring-client-api',
|
|
179
194
|
};
|
|
180
195
|
}
|
|
181
196
|
getGrantData(twoFactorAuthCode) {
|
|
@@ -348,6 +363,7 @@ class RingRestClient {
|
|
|
348
363
|
authorization: `Bearer ${authTokenResponse.access_token}`,
|
|
349
364
|
hardware_id: hardwareId,
|
|
350
365
|
'User-Agent': 'android:com.ringapp',
|
|
366
|
+
Connection: 'close',
|
|
351
367
|
},
|
|
352
368
|
});
|
|
353
369
|
}
|
package/lib/ring-camera.js
CHANGED
|
@@ -68,28 +68,37 @@ function cleanSnapshotUuid(uuid) {
|
|
|
68
68
|
return uuid.replace(/:.*$/, '');
|
|
69
69
|
}
|
|
70
70
|
class RingCamera extends subscribed_1.Subscribed {
|
|
71
|
+
initialData;
|
|
72
|
+
isDoorbot;
|
|
73
|
+
restClient;
|
|
74
|
+
avoidSnapshotBatteryDrain;
|
|
75
|
+
id;
|
|
76
|
+
deviceType;
|
|
77
|
+
model;
|
|
78
|
+
onData;
|
|
79
|
+
hasLight;
|
|
80
|
+
hasSiren;
|
|
81
|
+
onRequestUpdate = new rxjs_1.Subject();
|
|
82
|
+
onNewNotification = new rxjs_1.Subject();
|
|
83
|
+
onActiveNotifications = new rxjs_1.BehaviorSubject([]);
|
|
84
|
+
onDoorbellPressed = this.onNewNotification.pipe((0, operators_1.filter)((notification) => notification.android_config.category === ring_types_1.PushNotificationAction.Ding), (0, operators_1.share)());
|
|
85
|
+
onMotionDetected = this.onActiveNotifications.pipe((0, operators_1.map)((notifications) => notifications.some((notification) => notification.android_config.category ===
|
|
86
|
+
ring_types_1.PushNotificationAction.Motion)), (0, operators_1.distinctUntilChanged)(), (0, operators_1.share)({
|
|
87
|
+
connector: () => new rxjs_1.ReplaySubject(1),
|
|
88
|
+
resetOnError: false,
|
|
89
|
+
resetOnComplete: false,
|
|
90
|
+
resetOnRefCountZero: false,
|
|
91
|
+
}));
|
|
92
|
+
onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.map)(() => null), // no value needed, event is what matters
|
|
93
|
+
(0, operators_1.share)());
|
|
94
|
+
onBatteryLevel;
|
|
95
|
+
onInHomeDoorbellStatus;
|
|
71
96
|
constructor(initialData, isDoorbot, restClient, avoidSnapshotBatteryDrain) {
|
|
72
97
|
super();
|
|
73
98
|
this.initialData = initialData;
|
|
74
99
|
this.isDoorbot = isDoorbot;
|
|
75
100
|
this.restClient = restClient;
|
|
76
101
|
this.avoidSnapshotBatteryDrain = avoidSnapshotBatteryDrain;
|
|
77
|
-
this.onRequestUpdate = new rxjs_1.Subject();
|
|
78
|
-
this.onNewNotification = new rxjs_1.Subject();
|
|
79
|
-
this.onActiveNotifications = new rxjs_1.BehaviorSubject([]);
|
|
80
|
-
this.onDoorbellPressed = this.onNewNotification.pipe((0, operators_1.filter)((notification) => notification.android_config.category === ring_types_1.PushNotificationAction.Ding), (0, operators_1.share)());
|
|
81
|
-
this.onMotionDetected = this.onActiveNotifications.pipe((0, operators_1.map)((notifications) => notifications.some((notification) => notification.android_config.category ===
|
|
82
|
-
ring_types_1.PushNotificationAction.Motion)), (0, operators_1.distinctUntilChanged)(), (0, operators_1.share)({
|
|
83
|
-
connector: () => new rxjs_1.ReplaySubject(1),
|
|
84
|
-
resetOnError: false,
|
|
85
|
-
resetOnComplete: false,
|
|
86
|
-
resetOnRefCountZero: false,
|
|
87
|
-
}));
|
|
88
|
-
this.onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.map)(() => null), // no value needed, event is what matters
|
|
89
|
-
(0, operators_1.share)());
|
|
90
|
-
this.lastSnapshotTimestamp = 0;
|
|
91
|
-
this.lastSnapshotTimestampLocal = 0;
|
|
92
|
-
this.fetchingSnapshot = false;
|
|
93
102
|
this.id = this.initialData.id;
|
|
94
103
|
this.deviceType = this.initialData.kind;
|
|
95
104
|
this.model =
|
|
@@ -324,6 +333,9 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
324
333
|
? 600 * 1000 // battery cams only refresh timestamp every 10 minutes
|
|
325
334
|
: 10 * 1000; // snapshot updates will be forced. Limit to 10s lifetime
|
|
326
335
|
}
|
|
336
|
+
lastSnapshotTimestamp = 0;
|
|
337
|
+
lastSnapshotTimestampLocal = 0;
|
|
338
|
+
lastSnapshotPromise;
|
|
327
339
|
get currentTimestampAge() {
|
|
328
340
|
return Date.now() - this.lastSnapshotTimestampLocal;
|
|
329
341
|
}
|
|
@@ -352,6 +364,7 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
352
364
|
return false;
|
|
353
365
|
}
|
|
354
366
|
}
|
|
367
|
+
fetchingSnapshot = false;
|
|
355
368
|
async getSnapshot({ uuid } = {}) {
|
|
356
369
|
if (this.lastSnapshotPromise && this.shouldUseExistingSnapshotPromise()) {
|
|
357
370
|
return this.lastSnapshotPromise;
|
package/lib/ring-chime.js
CHANGED
|
@@ -11,10 +11,16 @@ const settingsWhichRequireReboot = [
|
|
|
11
11
|
'motion_audio_user_id',
|
|
12
12
|
];
|
|
13
13
|
class RingChime {
|
|
14
|
+
initialData;
|
|
15
|
+
restClient;
|
|
16
|
+
id;
|
|
17
|
+
deviceType;
|
|
18
|
+
model;
|
|
19
|
+
onData;
|
|
20
|
+
onRequestUpdate = new rxjs_1.Subject();
|
|
14
21
|
constructor(initialData, restClient) {
|
|
15
22
|
this.initialData = initialData;
|
|
16
23
|
this.restClient = restClient;
|
|
17
|
-
this.onRequestUpdate = new rxjs_1.Subject();
|
|
18
24
|
this.id = this.initialData.id;
|
|
19
25
|
this.deviceType = this.initialData.kind;
|
|
20
26
|
this.model = ring_types_1.ChimeModel[this.deviceType] || 'Chime';
|
package/lib/ring-device.js
CHANGED
|
@@ -7,6 +7,15 @@ const operators_1 = require("rxjs/operators");
|
|
|
7
7
|
const subscribed_1 = require("./subscribed");
|
|
8
8
|
const util_1 = require("./util");
|
|
9
9
|
class RingDevice extends subscribed_1.Subscribed {
|
|
10
|
+
initialData;
|
|
11
|
+
location;
|
|
12
|
+
assetId;
|
|
13
|
+
onData;
|
|
14
|
+
zid;
|
|
15
|
+
id;
|
|
16
|
+
deviceType;
|
|
17
|
+
categoryId;
|
|
18
|
+
onComponentDevices;
|
|
10
19
|
constructor(initialData, location, assetId) {
|
|
11
20
|
super();
|
|
12
21
|
this.initialData = initialData;
|
package/lib/ring-intercom.js
CHANGED
|
@@ -8,12 +8,18 @@ const operators_1 = require("rxjs/operators");
|
|
|
8
8
|
const ring_camera_1 = require("./ring-camera");
|
|
9
9
|
const util_1 = require("./util");
|
|
10
10
|
class RingIntercom {
|
|
11
|
+
initialData;
|
|
12
|
+
restClient;
|
|
13
|
+
id;
|
|
14
|
+
deviceType;
|
|
15
|
+
onData;
|
|
16
|
+
onRequestUpdate = new rxjs_1.Subject();
|
|
17
|
+
onBatteryLevel;
|
|
18
|
+
onDing = new rxjs_1.Subject();
|
|
19
|
+
onUnlocked = new rxjs_1.Subject();
|
|
11
20
|
constructor(initialData, restClient) {
|
|
12
21
|
this.initialData = initialData;
|
|
13
22
|
this.restClient = restClient;
|
|
14
|
-
this.onRequestUpdate = new rxjs_1.Subject();
|
|
15
|
-
this.onDing = new rxjs_1.Subject();
|
|
16
|
-
this.onUnlocked = new rxjs_1.Subject();
|
|
17
23
|
this.id = this.initialData.id;
|
|
18
24
|
this.deviceType = this.initialData.kind;
|
|
19
25
|
this.onData = new rxjs_1.BehaviorSubject(this.initialData);
|
|
@@ -17,16 +17,17 @@ const ringIceServers = [
|
|
|
17
17
|
'stun:stun4.l.google.com:19302',
|
|
18
18
|
];
|
|
19
19
|
class WeriftPeerConnection extends subscribed_1.Subscribed {
|
|
20
|
+
pc;
|
|
21
|
+
onAudioRtp = new rxjs_1.Subject();
|
|
22
|
+
onAudioRtcp = new rxjs_1.Subject();
|
|
23
|
+
onVideoRtp = new rxjs_1.Subject();
|
|
24
|
+
onVideoRtcp = new rxjs_1.Subject();
|
|
25
|
+
onIceCandidate = new rxjs_1.Subject();
|
|
26
|
+
onConnectionState = new rxjs_1.ReplaySubject(1);
|
|
27
|
+
returnAudioTrack = new werift_1.MediaStreamTrack({ kind: 'audio' });
|
|
28
|
+
onRequestKeyFrame = new rxjs_1.Subject();
|
|
20
29
|
constructor() {
|
|
21
30
|
super();
|
|
22
|
-
this.onAudioRtp = new rxjs_1.Subject();
|
|
23
|
-
this.onAudioRtcp = new rxjs_1.Subject();
|
|
24
|
-
this.onVideoRtp = new rxjs_1.Subject();
|
|
25
|
-
this.onVideoRtcp = new rxjs_1.Subject();
|
|
26
|
-
this.onIceCandidate = new rxjs_1.Subject();
|
|
27
|
-
this.onConnectionState = new rxjs_1.ReplaySubject(1);
|
|
28
|
-
this.returnAudioTrack = new werift_1.MediaStreamTrack({ kind: 'audio' });
|
|
29
|
-
this.onRequestKeyFrame = new rxjs_1.Subject();
|
|
30
31
|
const pc = (this.pc = new werift_1.RTCPeerConnection({
|
|
31
32
|
codecs: {
|
|
32
33
|
audio: [
|
|
@@ -98,7 +99,9 @@ class WeriftPeerConnection extends subscribed_1.Subscribed {
|
|
|
98
99
|
});
|
|
99
100
|
});
|
|
100
101
|
this.pc.onIceCandidate.subscribe((iceCandidate) => {
|
|
101
|
-
|
|
102
|
+
if (iceCandidate) {
|
|
103
|
+
this.onIceCandidate.next(iceCandidate);
|
|
104
|
+
}
|
|
102
105
|
});
|
|
103
106
|
pc.iceConnectionStateChange.subscribe(() => {
|
|
104
107
|
(0, util_1.logInfo)(`iceConnectionStateChange: ${pc.iceConnectionState}`);
|
|
@@ -6,10 +6,12 @@ function liveViewUrl(path) {
|
|
|
6
6
|
return `https://api.ring.com/integrations/v1/liveview/${path}`;
|
|
7
7
|
}
|
|
8
8
|
class SimpleWebRtcSession {
|
|
9
|
+
camera;
|
|
10
|
+
restClient;
|
|
11
|
+
sessionId = (0, util_1.generateUuid)();
|
|
9
12
|
constructor(camera, restClient) {
|
|
10
13
|
this.camera = camera;
|
|
11
14
|
this.restClient = restClient;
|
|
12
|
-
this.sessionId = (0, util_1.generateUuid)();
|
|
13
15
|
}
|
|
14
16
|
async start(sdp) {
|
|
15
17
|
const response = await this.restClient.request({
|
|
@@ -35,6 +35,17 @@ interface NotificationMessage {
|
|
|
35
35
|
text: string;
|
|
36
36
|
} & SessionBody;
|
|
37
37
|
}
|
|
38
|
+
interface CameraStartedMessage {
|
|
39
|
+
method: 'camera_started';
|
|
40
|
+
body: SessionBody;
|
|
41
|
+
}
|
|
42
|
+
interface StreamInfoMessage {
|
|
43
|
+
method: 'stream_info';
|
|
44
|
+
body: SessionBody & {
|
|
45
|
+
transcoding: boolean;
|
|
46
|
+
transcoding_reason: 'codec_mismatch' | string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
38
49
|
declare enum CloseReasonCode {
|
|
39
50
|
NormalClose = 0,
|
|
40
51
|
AuthenticationFailed = 5,
|
|
@@ -49,5 +60,5 @@ interface CloseMessage {
|
|
|
49
60
|
};
|
|
50
61
|
} & SessionBody;
|
|
51
62
|
}
|
|
52
|
-
export type IncomingMessage = AnswerMessage | IceCandidateMessage | SessionCreatedMessage | SessionStartedMessage | PongMessage | CloseMessage | NotificationMessage;
|
|
63
|
+
export type IncomingMessage = AnswerMessage | IceCandidateMessage | SessionCreatedMessage | SessionStartedMessage | PongMessage | CloseMessage | NotificationMessage | CameraStartedMessage | StreamInfoMessage;
|
|
53
64
|
export {};
|
|
@@ -17,19 +17,19 @@ function getCleanSdp(sdp, includeVideo) {
|
|
|
17
17
|
.join('\n');
|
|
18
18
|
}
|
|
19
19
|
class StreamingSession extends subscribed_1.Subscribed {
|
|
20
|
+
camera;
|
|
21
|
+
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();
|
|
20
29
|
constructor(camera, connection) {
|
|
21
30
|
super();
|
|
22
31
|
this.camera = camera;
|
|
23
32
|
this.connection = connection;
|
|
24
|
-
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
25
|
-
this.onUsingOpus = new rxjs_1.ReplaySubject(1);
|
|
26
|
-
this.onVideoRtp = new rxjs_1.Subject();
|
|
27
|
-
this.onAudioRtp = new rxjs_1.Subject();
|
|
28
|
-
this.audioSplitter = new camera_utils_1.RtpSplitter();
|
|
29
|
-
this.videoSplitter = new camera_utils_1.RtpSplitter();
|
|
30
|
-
this.returnAudioSplitter = new camera_utils_1.RtpSplitter();
|
|
31
|
-
this.cameraSpeakerActivated = false;
|
|
32
|
-
this.hasEnded = false;
|
|
33
33
|
this.bindToConnection(connection);
|
|
34
34
|
}
|
|
35
35
|
bindToConnection(connection) {
|
|
@@ -44,6 +44,7 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
44
44
|
activate() {
|
|
45
45
|
this.requestKeyFrame();
|
|
46
46
|
}
|
|
47
|
+
cameraSpeakerActivated = false;
|
|
47
48
|
activateCameraSpeaker() {
|
|
48
49
|
if (this.cameraSpeakerActivated || this.hasEnded) {
|
|
49
50
|
return;
|
|
@@ -155,6 +156,7 @@ class StreamingSession extends subscribed_1.Subscribed {
|
|
|
155
156
|
});
|
|
156
157
|
this.onCallEnded.pipe((0, operators_1.take)(1)).subscribe(() => ff.stop());
|
|
157
158
|
}
|
|
159
|
+
hasEnded = false;
|
|
158
160
|
callEnded() {
|
|
159
161
|
if (this.hasEnded) {
|
|
160
162
|
return;
|
|
@@ -8,19 +8,23 @@ const util_1 = require("../util");
|
|
|
8
8
|
const peer_connection_1 = require("./peer-connection");
|
|
9
9
|
const subscribed_1 = require("../subscribed");
|
|
10
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();
|
|
20
|
+
onWsOpen;
|
|
21
|
+
onAudioRtp;
|
|
22
|
+
onVideoRtp;
|
|
23
|
+
pc;
|
|
24
|
+
ws;
|
|
11
25
|
constructor(ticket, camera, options) {
|
|
12
26
|
super();
|
|
13
27
|
this.camera = camera;
|
|
14
|
-
this.onSessionId = new rxjs_1.ReplaySubject(1);
|
|
15
|
-
this.onOfferSent = new rxjs_1.ReplaySubject(1);
|
|
16
|
-
this.dialogId = (0, util_1.generateUuid)();
|
|
17
|
-
this.onCameraConnected = new rxjs_1.ReplaySubject(1);
|
|
18
|
-
this.onCallAnswered = new rxjs_1.ReplaySubject(1);
|
|
19
|
-
this.onCallEnded = new rxjs_1.ReplaySubject(1);
|
|
20
|
-
this.onError = new rxjs_1.ReplaySubject(1);
|
|
21
|
-
this.onMessage = new rxjs_1.ReplaySubject();
|
|
22
|
-
this.sessionId = null;
|
|
23
|
-
this.hasEnded = false;
|
|
24
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
29
|
headers: {
|
|
26
30
|
// This must exist or the socket will close immediately but content does not seem to matter
|
|
@@ -110,6 +114,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
|
|
|
110
114
|
});
|
|
111
115
|
this.onOfferSent.next();
|
|
112
116
|
}
|
|
117
|
+
sessionId = null;
|
|
113
118
|
async handleMessage(message) {
|
|
114
119
|
if (message.body.doorbot_id !== this.camera.id) {
|
|
115
120
|
// ignore messages for other cameras
|
|
@@ -159,6 +164,10 @@ class WebrtcConnection extends subscribed_1.Subscribed {
|
|
|
159
164
|
(0, util_1.logError)(message.body);
|
|
160
165
|
this.callEnded();
|
|
161
166
|
return;
|
|
167
|
+
case 'camera_started':
|
|
168
|
+
case 'stream_info':
|
|
169
|
+
// ignore these messages as we don't use them
|
|
170
|
+
return;
|
|
162
171
|
}
|
|
163
172
|
(0, util_1.logError)('UNKNOWN MESSAGE');
|
|
164
173
|
(0, util_1.logError)(message);
|
|
@@ -220,6 +229,7 @@ class WebrtcConnection extends subscribed_1.Subscribed {
|
|
|
220
229
|
});
|
|
221
230
|
}));
|
|
222
231
|
}
|
|
232
|
+
hasEnded = false;
|
|
223
233
|
callEnded() {
|
|
224
234
|
if (this.hasEnded) {
|
|
225
235
|
return;
|
package/lib/subscribed.js
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Subscribed = void 0;
|
|
4
4
|
class Subscribed {
|
|
5
|
-
|
|
6
|
-
this.subscriptions = [];
|
|
7
|
-
}
|
|
5
|
+
subscriptions = [];
|
|
8
6
|
addSubscriptions(...subscriptions) {
|
|
9
7
|
this.subscriptions.push(...subscriptions);
|
|
10
8
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ring-client-api",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.2.0-beta.1",
|
|
4
4
|
"description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./lib/index.js",
|
|
8
8
|
"./ffmpeg": "./lib/ffmpeg.js",
|
|
9
9
|
"./util": "./lib/util.js",
|
|
10
|
-
"./rest-client": "./lib/rest-client.js"
|
|
10
|
+
"./rest-client": "./lib/rest-client.js",
|
|
11
|
+
"./streaming/streaming-session": "./lib/streaming/streaming-session.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "rm -rf lib && tsc --declaration && chmod +x ./lib/ring-*-cli.js",
|
|
@@ -17,35 +18,35 @@
|
|
|
17
18
|
"test:watch": "jest --watch"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@eneris/push-receiver": "4.
|
|
21
|
-
"@homebridge/camera-utils": "^2.2.
|
|
22
|
-
"@types/socket.io-client": "1.4.36",
|
|
21
|
+
"@eneris/push-receiver": "4.3.0",
|
|
22
|
+
"@homebridge/camera-utils": "^2.2.7-beta",
|
|
23
23
|
"colors": "1.4.0",
|
|
24
|
-
"debug": "^4.3.
|
|
24
|
+
"debug": "^4.3.7",
|
|
25
25
|
"json-bigint": "^1.0.0",
|
|
26
26
|
"rxjs": "^7.8.1",
|
|
27
27
|
"socket.io-client": "^2.5.0",
|
|
28
|
-
"systeminformation": "^5.23.
|
|
29
|
-
"uuid": "^
|
|
30
|
-
"werift": "0.
|
|
28
|
+
"systeminformation": "^5.23.5",
|
|
29
|
+
"uuid": "^11.0.2",
|
|
30
|
+
"werift": "0.20.1",
|
|
31
31
|
"ws": "^8.18.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/debug": "4.1.12",
|
|
35
|
-
"@types/jest": "29.5.
|
|
35
|
+
"@types/jest": "29.5.14",
|
|
36
36
|
"@types/json-bigint": "^1.0.4",
|
|
37
|
-
"@types/node": "22.
|
|
37
|
+
"@types/node": "22.8.6",
|
|
38
|
+
"@types/socket.io-client": "1.4.36",
|
|
38
39
|
"@types/uuid": "10.0.0",
|
|
39
|
-
"@types/ws": "^8.5.
|
|
40
|
+
"@types/ws": "^8.5.13",
|
|
40
41
|
"eslint-config-shared": "*",
|
|
41
42
|
"jest": "29.7.0",
|
|
42
|
-
"msw": "^2.
|
|
43
|
-
"ts-jest": "29.2.
|
|
43
|
+
"msw": "^2.6.0",
|
|
44
|
+
"ts-jest": "29.2.5",
|
|
44
45
|
"tsconfig": "*",
|
|
45
|
-
"typescript": "5.
|
|
46
|
+
"typescript": "5.6.3"
|
|
46
47
|
},
|
|
47
48
|
"engines": {
|
|
48
|
-
"node": "^18 || ^20"
|
|
49
|
+
"node": "^18 || ^20 || ^22"
|
|
49
50
|
},
|
|
50
51
|
"repository": {
|
|
51
52
|
"type": "git",
|