ring-client-api 9.25.0-beta.0 → 9.25.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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [9.25.0-beta.1](https://github.com/dgreif/ring/compare/v9.25.0-beta.0...v9.25.0-beta.1) (2022-02-14)
6
+
7
+
8
+ ### Features
9
+
10
+ * use new WebRTC endpoint for live calls ([6e7779f](https://github.com/dgreif/ring/commit/6e7779feef9789583921fc44bab32c099f3026d2))
11
+
5
12
  ## [9.25.0-beta.0](https://github.com/dgreif/ring/compare/v9.24.0...v9.25.0-beta.0) (2022-01-29)
6
13
 
7
14
 
@@ -0,0 +1,41 @@
1
+ import { ReplaySubject } from 'rxjs';
2
+ import { RingCamera } from './ring-camera';
3
+ import { Subscribed } from './subscribed';
4
+ import { RtpPacket } from '@koush/werift';
5
+ declare type SpawnInput = string | number;
6
+ export interface FfmpegOptions {
7
+ input?: SpawnInput[];
8
+ video?: SpawnInput[] | false;
9
+ audio?: SpawnInput[];
10
+ output: SpawnInput[];
11
+ }
12
+ export declare class LiveCall extends Subscribed {
13
+ private sessionId;
14
+ private camera;
15
+ private readonly ws;
16
+ private readonly onWsOpen;
17
+ private readonly onMessage;
18
+ private readonly pc;
19
+ readonly onCallAnswered: ReplaySubject<void>;
20
+ readonly onCallEnded: ReplaySubject<void>;
21
+ private readonly audioSplitter;
22
+ private readonly videoSplitter;
23
+ readonly onVideoRtp: import("rxjs").Subject<RtpPacket>;
24
+ readonly onAudioRtp: import("rxjs").Subject<RtpPacket>;
25
+ constructor(sessionId: string, camera: RingCamera);
26
+ private handleMessage;
27
+ reservePort(bufferPorts?: number): Promise<number>;
28
+ prepareTranscoder(transcodeVideoStream: boolean, ffmpegInputOptions: SpawnInput[] | undefined, audioPort: number, videoPort: number, sdpInput: string): {
29
+ ffmpegInputArguments: SpawnInput[];
30
+ inputSdpLines: string[];
31
+ };
32
+ startTranscoding(ffmpegOptions: FfmpegOptions): Promise<void>;
33
+ activated: boolean;
34
+ activate(): Promise<void>;
35
+ activateCameraSpeaker(): Promise<void>;
36
+ private sendMessage;
37
+ private callEnded;
38
+ stop(): void;
39
+ sendAudioPacket(rtp: RtpPacket): void;
40
+ }
41
+ export {};
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.LiveCall = void 0;
13
+ const rtp_utils_1 = require("./rtp-utils");
14
+ const ws_1 = require("ws");
15
+ const rxjs_1 = require("rxjs");
16
+ const peer_connection_1 = require("./peer-connection");
17
+ const util_1 = require("./util");
18
+ const operators_1 = require("rxjs/operators");
19
+ const subscribed_1 = require("./subscribed");
20
+ const camera_utils_1 = require("@homebridge/camera-utils");
21
+ const ffmpeg_1 = require("./ffmpeg");
22
+ class LiveCall extends subscribed_1.Subscribed {
23
+ constructor(sessionId, camera) {
24
+ super();
25
+ this.sessionId = sessionId;
26
+ this.camera = camera;
27
+ this.onCallAnswered = new rxjs_1.ReplaySubject(1);
28
+ this.onCallEnded = new rxjs_1.ReplaySubject(1);
29
+ this.audioSplitter = new camera_utils_1.RtpSplitter();
30
+ this.videoSplitter = new camera_utils_1.RtpSplitter();
31
+ this.activated = false;
32
+ const liveCallSession = (0, rtp_utils_1.parseLiveCallSession)(sessionId);
33
+ this.pc = new peer_connection_1.PeerConnection();
34
+ this.ws = new ws_1.WebSocket(`wss://${liveCallSession.rms_fqdn}:${liveCallSession.webrtc_port}/`, {
35
+ headers: {
36
+ API_VERSION: '3.1',
37
+ API_TOKEN: sessionId,
38
+ CLIENT_INFO: 'Ring/3.48.0;Platform/Android;OS/7.0;Density/2.0;Device/samsung-SM-T710;Locale/en-US;TimeZone/GMT-07:00',
39
+ },
40
+ });
41
+ this.onAudioRtp = this.pc.onAudioRtp;
42
+ this.onVideoRtp = this.pc.onVideoRtp;
43
+ this.onMessage = (0, rxjs_1.fromEvent)(this.ws, 'message');
44
+ this.onWsOpen = (0, rxjs_1.fromEvent)(this.ws, 'open');
45
+ const onError = (0, rxjs_1.fromEvent)(this.ws, 'error'), onClose = (0, rxjs_1.fromEvent)(this.ws, 'close');
46
+ this.addSubscriptions(this.onMessage
47
+ .pipe((0, operators_1.concatMap)((message) => {
48
+ return this.handleMessage(JSON.parse(message.data));
49
+ }))
50
+ .subscribe(), this.onWsOpen.subscribe(() => {
51
+ (0, util_1.logDebug)(`WebSocket connected for ${this.camera.name}`);
52
+ }), onError.subscribe((e) => {
53
+ (0, util_1.logError)(e);
54
+ this.callEnded();
55
+ }), onClose.subscribe(() => {
56
+ this.callEnded();
57
+ }));
58
+ }
59
+ handleMessage(message) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ switch (message.method) {
62
+ case 'sdp':
63
+ const answer = yield this.pc.createAnswer(message);
64
+ this.sendMessage(Object.assign({ method: 'sdp' }, answer));
65
+ this.onCallAnswered.next();
66
+ return;
67
+ case 'ice':
68
+ yield this.pc.addIceCandidate({
69
+ candidate: message.ice,
70
+ sdpMLineIndex: message.mlineindex,
71
+ });
72
+ return;
73
+ }
74
+ });
75
+ }
76
+ reservePort(bufferPorts = 0) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ const ports = yield (0, camera_utils_1.reservePorts)({ count: bufferPorts + 1 });
79
+ return ports[0];
80
+ });
81
+ }
82
+ prepareTranscoder(transcodeVideoStream, ffmpegInputOptions, audioPort, videoPort, sdpInput) {
83
+ const ffmpegInputArguments = [
84
+ '-hide_banner',
85
+ '-protocol_whitelist',
86
+ 'pipe,udp,rtp,file,crypto',
87
+ '-acodec',
88
+ 'libopus',
89
+ '-f',
90
+ 'sdp',
91
+ ...(ffmpegInputOptions || []),
92
+ '-i',
93
+ sdpInput,
94
+ ], inputSdpLines = [
95
+ 'v=0',
96
+ 'o=105202070 3747 461 IN IP4 127.0.0.1',
97
+ 's=Talk',
98
+ 'c=IN IP4 127.0.0.1',
99
+ 'b=AS:380',
100
+ 't=0 0',
101
+ 'a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics',
102
+ `m=audio ${audioPort} RTP/SAVP 101`,
103
+ 'a=rtpmap:101 OPUS/48000/2',
104
+ 'a=rtcp-fb:101 nack pli',
105
+ 'a=fmtp:101 useinbandfec=1;sprop-stereo=0',
106
+ 'a=rtcp-mux',
107
+ ];
108
+ if (transcodeVideoStream) {
109
+ inputSdpLines.push(`m=video ${videoPort} RTP/SAVP 96`, 'a=rtpmap:96 H264/90000', 'a=rtcp-fb:96 nack', 'a=rtcp-fb:96 nack pli', 'a=fmtp:96 packetization-mode=1;profile-level-id=640029;level-asymmetry-allowed=1', 'a=rtcp-mux');
110
+ this.addSubscriptions(this.onVideoRtp
111
+ .pipe((0, operators_1.concatMap)((rtp) => {
112
+ return this.videoSplitter.send(rtp.serialize(), {
113
+ port: videoPort,
114
+ });
115
+ }))
116
+ .subscribe());
117
+ }
118
+ this.addSubscriptions(this.onAudioRtp
119
+ .pipe((0, operators_1.concatMap)((rtp) => {
120
+ return this.audioSplitter.send(rtp.serialize(), {
121
+ port: audioPort,
122
+ });
123
+ }))
124
+ .subscribe());
125
+ return {
126
+ ffmpegInputArguments,
127
+ inputSdpLines,
128
+ };
129
+ }
130
+ startTranscoding(ffmpegOptions) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ const videoPort = yield this.reservePort(1), audioPort = yield this.reservePort(1), transcodeVideoStream = ffmpegOptions.video !== false, { ffmpegInputArguments, inputSdpLines } = this.prepareTranscoder(transcodeVideoStream, ffmpegOptions.input, audioPort, videoPort, 'pipe:'), ff = new camera_utils_1.FfmpegProcess({
133
+ ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
134
+ ? ffmpegOptions.video || ['-vcodec', 'copy']
135
+ : []), ...(ffmpegOptions.output || [])),
136
+ ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
137
+ exitCallback: () => this.callEnded(),
138
+ logLabel: `From Ring (${this.camera.name})`,
139
+ logger: {
140
+ error: util_1.logError,
141
+ info: util_1.logDebug,
142
+ },
143
+ });
144
+ this.onCallEnded.subscribe(() => ff.stop());
145
+ ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n'));
146
+ // Activate the stream now that ffmpeg is ready to receive
147
+ yield this.activate();
148
+ });
149
+ }
150
+ activate() {
151
+ return __awaiter(this, void 0, void 0, function* () {
152
+ if (this.activated) {
153
+ return;
154
+ }
155
+ this.activated = true;
156
+ yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
157
+ this.sendMessage({ method: 'activate_session' });
158
+ this.sendMessage({
159
+ video_enabled: true,
160
+ audio_enabled: true,
161
+ method: 'stream_options',
162
+ });
163
+ });
164
+ }
165
+ activateCameraSpeaker() {
166
+ return __awaiter(this, void 0, void 0, function* () {
167
+ yield (0, rxjs_1.firstValueFrom)(this.onCallAnswered);
168
+ this.sendMessage({
169
+ stealth_mode: false,
170
+ method: 'camera_options',
171
+ });
172
+ });
173
+ }
174
+ sendMessage(message) {
175
+ this.ws.send(JSON.stringify(message));
176
+ }
177
+ callEnded() {
178
+ try {
179
+ this.sendMessage({
180
+ reason: { code: 0, text: '' },
181
+ method: 'close',
182
+ });
183
+ this.ws.close();
184
+ }
185
+ catch (_) {
186
+ // ignore any errors since we are stopping the call
187
+ }
188
+ this.unsubscribe();
189
+ this.onCallEnded.next();
190
+ this.pc.close();
191
+ this.audioSplitter.close();
192
+ this.videoSplitter.close();
193
+ }
194
+ stop() {
195
+ this.callEnded();
196
+ }
197
+ sendAudioPacket(rtp) {
198
+ this.pc.returnAudioTrack.writeRtp(rtp);
199
+ }
200
+ }
201
+ exports.LiveCall = LiveCall;
@@ -0,0 +1,17 @@
1
+ import { MediaStreamTrack, RTCIceCandidate, RTCPeerConnection, RtcpPacket, RtpPacket } from '@koush/werift';
2
+ import { Subject } from 'rxjs';
3
+ export declare class PeerConnection {
4
+ pc: RTCPeerConnection;
5
+ onAudioRtp: Subject<RtpPacket>;
6
+ onAudioRtcp: Subject<RtcpPacket>;
7
+ onVideoRtp: Subject<RtpPacket>;
8
+ onVideoRtcp: Subject<RtcpPacket>;
9
+ returnAudioTrack: MediaStreamTrack;
10
+ constructor();
11
+ createAnswer(offer: {
12
+ type: 'offer';
13
+ sdp: string;
14
+ }): Promise<import("@koush/werift").RTCSessionDescription>;
15
+ addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
16
+ close(): void;
17
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PeerConnection = void 0;
13
+ const werift_1 = require("@koush/werift");
14
+ const rxjs_1 = require("rxjs");
15
+ const util_1 = require("./util");
16
+ const debug = false;
17
+ class PeerConnection {
18
+ constructor() {
19
+ this.onAudioRtp = new rxjs_1.Subject();
20
+ this.onAudioRtcp = new rxjs_1.Subject();
21
+ this.onVideoRtp = new rxjs_1.Subject();
22
+ this.onVideoRtcp = new rxjs_1.Subject();
23
+ this.returnAudioTrack = new werift_1.MediaStreamTrack({ kind: 'audio' });
24
+ const pc = (this.pc = new werift_1.RTCPeerConnection({
25
+ codecs: {
26
+ audio: [
27
+ new werift_1.RTCRtpCodecParameters({
28
+ mimeType: 'audio/opus',
29
+ clockRate: 48000,
30
+ channels: 2,
31
+ }),
32
+ ],
33
+ video: [
34
+ new werift_1.RTCRtpCodecParameters({
35
+ mimeType: 'video/H264',
36
+ clockRate: 90000,
37
+ rtcpFeedback: [
38
+ { type: 'transport-cc' },
39
+ { type: 'ccm', parameter: 'fir' },
40
+ { type: 'nack' },
41
+ { type: 'nack', parameter: 'pli' },
42
+ { type: 'goog-remb' },
43
+ ],
44
+ parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
45
+ }),
46
+ ],
47
+ },
48
+ })), audioTransceiver = pc.addTransceiver(this.returnAudioTrack, {
49
+ direction: 'sendrecv',
50
+ }), videoTransceiver = pc.addTransceiver('video', {
51
+ direction: 'recvonly',
52
+ });
53
+ audioTransceiver.onTrack.subscribe((track) => {
54
+ track.onReceiveRtp.subscribe((rtp) => {
55
+ this.onAudioRtp.next(rtp);
56
+ });
57
+ track.onReceiveRtcp.subscribe((rtcp) => {
58
+ this.onAudioRtcp.next(rtcp);
59
+ });
60
+ if (debug) {
61
+ track.onReceiveRtp.once(() => {
62
+ (0, util_1.logInfo)('received first audio packet');
63
+ });
64
+ }
65
+ });
66
+ videoTransceiver.onTrack.subscribe((track) => {
67
+ track.onReceiveRtp.subscribe((rtp) => {
68
+ this.onVideoRtp.next(rtp);
69
+ });
70
+ track.onReceiveRtcp.subscribe((rtcp) => {
71
+ this.onVideoRtcp.next(rtcp);
72
+ });
73
+ track.onReceiveRtp.once(() => {
74
+ if (debug) {
75
+ (0, util_1.logInfo)('received first video packet');
76
+ }
77
+ setInterval(() => videoTransceiver.receiver.sendRtcpPLI(track.ssrc), 2000);
78
+ });
79
+ });
80
+ }
81
+ createAnswer(offer) {
82
+ return __awaiter(this, void 0, void 0, function* () {
83
+ yield this.pc.setRemoteDescription(offer);
84
+ const answer = yield this.pc.createAnswer();
85
+ yield this.pc.setLocalDescription(answer);
86
+ return answer;
87
+ });
88
+ }
89
+ addIceCandidate(candidate) {
90
+ return this.pc.addIceCandidate(candidate);
91
+ }
92
+ close() {
93
+ this.pc.close().catch(util_1.logError);
94
+ }
95
+ }
96
+ exports.PeerConnection = PeerConnection;
@@ -7,6 +7,7 @@ import { DeepPartial } from './util';
7
7
  import { FfmpegOptions, SipSession } from './sip-session';
8
8
  import { SipOptions } from './sip-call';
9
9
  import { Subscribed } from './subscribed';
10
+ import { LiveCall } from './live-call';
10
11
  export declare function getBatteryLevel(data: Pick<CameraData, 'battery_life' | 'battery_life_2'>): number | null;
11
12
  export declare function getSearchQueryString(options: CameraEventOptions | (HistoryOptions & {
12
13
  accountId: string;
@@ -54,6 +55,7 @@ export declare class RingCamera extends Subscribed {
54
55
  getDeviceSettings(): Promise<CameraDeviceSettingsData & import("./rest-client").ExtendedResponse>;
55
56
  setInHomeDoorbell(enable: boolean): Promise<boolean>;
56
57
  getHealth(): Promise<CameraHealth>;
58
+ startLiveCall(): Promise<LiveCall>;
57
59
  startVideoOnDemand(): Promise<("" | ActiveDing) & import("./rest-client").ExtendedResponse>;
58
60
  private pollForActiveDing;
59
61
  private expiredDingIds;
@@ -18,6 +18,7 @@ const camera_utils_1 = require("@homebridge/camera-utils");
18
18
  const util_1 = require("./util");
19
19
  const sip_session_1 = require("./sip-session");
20
20
  const subscribed_1 = require("./subscribed");
21
+ const live_call_1 = require("./live-call");
21
22
  const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
22
23
  function parseBatteryLife(batteryLife) {
23
24
  if (batteryLife === null || batteryLife === undefined) {
@@ -231,6 +232,25 @@ class RingCamera extends subscribed_1.Subscribed {
231
232
  return response.device_health;
232
233
  });
233
234
  }
235
+ startLiveCall() {
236
+ return __awaiter(this, void 0, void 0, function* () {
237
+ const liveCall = yield this.restClient
238
+ .request({
239
+ method: 'POST',
240
+ url: this.doorbotUrl('live_call'),
241
+ })
242
+ .catch((e) => {
243
+ var _a;
244
+ if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.statusCode) === 403) {
245
+ 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.`;
246
+ (0, util_1.logError)(errorMessage);
247
+ throw new Error(errorMessage);
248
+ }
249
+ throw e;
250
+ });
251
+ return new live_call_1.LiveCall(liveCall.data.session_id, this);
252
+ });
253
+ }
234
254
  startVideoOnDemand() {
235
255
  return this.restClient
236
256
  .request({
@@ -439,10 +459,11 @@ class RingCamera extends subscribed_1.Subscribed {
439
459
  }
440
460
  recordToFile(outputPath, duration = 30) {
441
461
  return __awaiter(this, void 0, void 0, function* () {
442
- const sipSession = yield this.streamVideo({
462
+ const liveCall = yield this.startLiveCall();
463
+ yield liveCall.startTranscoding({
443
464
  output: ['-t', duration.toString(), outputPath],
444
465
  });
445
- yield (0, rxjs_1.firstValueFrom)(sipSession.onCallEnded);
466
+ yield (0, rxjs_1.firstValueFrom)(liveCall.onCallEnded);
446
467
  });
447
468
  }
448
469
  streamVideo(ffmpegOptions) {
@@ -688,6 +688,21 @@ export interface ActiveDing {
688
688
  sip_token: string;
689
689
  sip_ding_id: string;
690
690
  }
691
+ export interface LiveCallResponse {
692
+ ding_id: string;
693
+ ding_kind: DingKind;
694
+ device_id: number;
695
+ device_kind: RingCameraKind;
696
+ device_description: string;
697
+ state: DingState;
698
+ protocol: 'ring_media_server' | unknown;
699
+ now: number;
700
+ video_jitter_buffer_ms: number;
701
+ audio_jitter_buffer_ms: number;
702
+ data: {
703
+ session_id: string;
704
+ };
705
+ }
691
706
  export interface AuthTokenResponse {
692
707
  access_token: string;
693
708
  expires_in: number;
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { RtpSplitter, SrtpOptions } from '@homebridge/camera-utils';
3
+ import { DingKind } from './ring-types';
3
4
  export interface RtpStreamOptions extends SrtpOptions {
4
5
  port: number;
5
6
  rtcpPort: number;
@@ -27,3 +28,28 @@ export declare function sendStunBindingRequest({ rtpDescription, rtpSplitter, rt
27
28
  type: 'video' | 'audio';
28
29
  }): void;
29
30
  export declare function createStunResponder(rtpSplitter: RtpSplitter): void;
31
+ export interface LiveCallSession {
32
+ alexa_port: 18443;
33
+ app_session_token: string;
34
+ availability_zone: 'availability-zone';
35
+ custom_timer: {
36
+ max_sec: number;
37
+ };
38
+ ding_id: string;
39
+ ding_kind: DingKind;
40
+ doorbot_id: number;
41
+ exp: number;
42
+ ip: string;
43
+ port: number;
44
+ private_ip: string;
45
+ rms_fqdn: string;
46
+ rms_version: string;
47
+ rsp_port: number;
48
+ rtsp_port: number;
49
+ session_id: string;
50
+ sip_port: number;
51
+ webrtc_port: number;
52
+ webrtc_url: string;
53
+ wwr_port: number;
54
+ }
55
+ export declare function parseLiveCallSession(sessionId: string): LiveCallSession;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createStunResponder = exports.sendStunBindingRequest = exports.isStunMessage = void 0;
3
+ exports.parseLiveCallSession = exports.createStunResponder = exports.sendStunBindingRequest = exports.isStunMessage = void 0;
4
4
  const util_1 = require("./util");
5
5
  const stun = require('stun');
6
6
  const stunMagicCookie = 0x2112a442; // https://tools.ietf.org/html/rfc5389#section-6
@@ -54,3 +54,8 @@ function createStunResponder(rtpSplitter) {
54
54
  });
55
55
  }
56
56
  exports.createStunResponder = createStunResponder;
57
+ function parseLiveCallSession(sessionId) {
58
+ const encodedSession = sessionId.split('.')[1], buff = new Buffer(encodedSession, 'base64'), text = buff.toString('ascii');
59
+ return JSON.parse(text);
60
+ }
61
+ exports.parseLiveCallSession = parseLiveCallSession;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ring-client-api",
3
- "version": "9.25.0-beta.0",
3
+ "version": "9.25.0-beta.1",
4
4
  "description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
5
5
  "main": "lib/api/index.js",
6
6
  "bin": {
@@ -14,14 +14,16 @@
14
14
  "watch:tests": "jest --watch",
15
15
  "lint": "eslint '**/*.ts' --fix",
16
16
  "build": "rm -rf lib && tsc --declaration",
17
- "example": "npm run build && node ./lib/examples/example.js",
18
- "api-example": "npm run build && node ./lib/examples/api-example.js",
19
- "record-example": "npm run build && node ./lib/examples/record-example.js",
20
- "stream-example": "npm run build && node ./lib/examples/stream-example.js",
21
- "browser-example": "npm run build && node ./lib/examples/browser-example.js",
22
- "auth-cli": "npm run build && ./ring-auth-cli.js",
23
- "device-data-cli": "npm run build && ./ring-device-data-cli.js",
24
- "hb": "npm run build && ./build/package-homebridge.sh && homebridge -P ./release-homebridge -U ./.homebridge",
17
+ "build-quick": "rm -rf lib && esbuild ./{api,examples,homebridge}/*.ts ./api/*.js --target=es2017 --format=cjs --outdir=lib",
18
+ "example": "npm run build-quick && node ./lib/examples/example.js",
19
+ "api-example": "npm run build-quick && node ./lib/examples/api-example.js",
20
+ "record-example": "npm run build-quick && node ./lib/examples/record-example.js",
21
+ "return-audio-example": "npm run build-quick && node ./lib/examples/return-audio-example.js",
22
+ "stream-example": "npm run build-quick && node ./lib/examples/stream-example.js",
23
+ "browser-example": "npm run build-quick && node ./lib/examples/browser-example.js",
24
+ "auth-cli": "npm run build-quick && ./ring-auth-cli.js",
25
+ "device-data-cli": "npm run build-quick && ./ring-device-data-cli.js",
26
+ "hb": "npm run build-quick && ./build/package-homebridge.sh && homebridge -P ./release-homebridge -U ./.homebridge",
25
27
  "prepare-npm-packages": "./build/package-api.sh && ./build/package-homebridge.sh",
26
28
  "push-with-tags": "git push --follow-tags && node ./build/github-releaser.js",
27
29
  "deploy": "git pull -r && standard-version && npm publish ./release-api && npm publish ./release-homebridge && npm run push-with-tags",
@@ -53,8 +55,9 @@
53
55
  "author": "dgreif",
54
56
  "license": "MIT",
55
57
  "dependencies": {
56
- "@homebridge/camera-utils": "2.0.4",
58
+ "@homebridge/camera-utils": "2.1.0",
57
59
  "@homebridge/plugin-ui-utils": "0.0.19",
60
+ "@koush/werift": "0.14.5-beta5",
58
61
  "@types/socket.io-client": "1.4.36",
59
62
  "colors": "1.4.0",
60
63
  "debug": "4.3.3",
@@ -64,18 +67,21 @@
64
67
  "socket.io-client": "2.4.0",
65
68
  "stun": "2.1.0",
66
69
  "systeminformation": "5.10.7",
67
- "uuid": "8.3.2"
70
+ "uuid": "8.3.2",
71
+ "ws": "8.4.2"
68
72
  },
69
73
  "devDependencies": {
70
74
  "@types/debug": "4.1.7",
71
75
  "@types/jest": "27.4.0",
72
76
  "@types/node": "17.0.10",
73
77
  "@types/uuid": "8.3.4",
78
+ "@types/ws": "^8.2.2",
74
79
  "@typescript-eslint/eslint-plugin": "5.10.0",
75
80
  "@typescript-eslint/parser": "5.10.0",
76
81
  "concurrently": "^7.0.0",
77
82
  "conventional-github-releaser": "3.1.5",
78
83
  "dotenv": "14.2.0",
84
+ "esbuild": "^0.14.19",
79
85
  "eslint": "8.7.0",
80
86
  "eslint-config-prettier": "8.3.0",
81
87
  "eslint-plugin-jest": "25.7.0",