Upgrade to RingRTC v2.8.2 RC.6

This commit is contained in:
Evan Hahn 2020-11-17 13:49:48 -06:00 committed by Josh Perez
parent 4bf5a24efb
commit b366967ca5
15 changed files with 214 additions and 183 deletions

View file

@ -136,7 +136,7 @@
"redux-ts-utils": "3.2.2",
"reselect": "4.0.0",
"rimraf": "2.6.2",
"ringrtc": "https://github.com/signalapp/signal-ringrtc-node.git#7de95cffa71019e94c74fc43313c28eaf23c66e1",
"ringrtc": "https://github.com/signalapp/signal-ringrtc-node.git#65fbea67295005fe1a9db2cbe85fce0d5297848d",
"sanitize-filename": "1.6.3",
"sanitize.css": "11.0.0",
"semver": "5.4.1",

View file

@ -62,17 +62,10 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
cancelCall: action('cancel-call'),
closeNeedPermissionScreen: action('close-need-permission-screen'),
declineCall: action('decline-call'),
// We allow `any` here because these are fake and actually come from RingRTC, which we
// We allow `any` here because this is fake and actually comes from RingRTC, which we
// can't import.
/* eslint-disable @typescript-eslint/no-explicit-any */
createCanvasVideoRenderer: () =>
({
setCanvas: noop,
enable: noop,
disable: noop,
} as any),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any,
/* eslint-enable @typescript-eslint/no-explicit-any */
hangUp: action('hang-up'),
i18n,
me: {

View file

@ -12,7 +12,6 @@ import {
CallEndedReason,
CallMode,
CallState,
CanvasVideoRenderer,
GroupCallJoinState,
GroupCallRemoteParticipantType,
VideoFrameSource,
@ -47,7 +46,6 @@ export interface PropsType {
activeCall?: ActiveCallType;
availableCameras: Array<MediaDeviceInfo>;
cancelCall: (_: CancelCallType) => void;
createCanvasVideoRenderer: () => CanvasVideoRenderer;
closeNeedPermissionScreen: () => void;
getGroupCallVideoFrameSource: (
conversationId: string,
@ -89,7 +87,6 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
availableCameras,
cancelCall,
closeNeedPermissionScreen,
createCanvasVideoRenderer,
hangUp,
i18n,
getGroupCallVideoFrameSource,
@ -210,7 +207,6 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
<CallingPip
call={call}
conversation={conversation}
createCanvasVideoRenderer={createCanvasVideoRenderer}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
hangUp={hangUp}
hasLocalVideo={hasLocalVideo}
@ -227,7 +223,6 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
<CallScreen
call={call}
conversation={conversation}
createCanvasVideoRenderer={createCanvasVideoRenderer}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
hangUp={hangUp}
hasLocalAudio={hasLocalAudio}

View file

@ -78,17 +78,10 @@ const createProps = (
type: 'direct',
lastUpdated: Date.now(),
},
// We allow `any` here because these are fake and actually come from RingRTC, which we
// We allow `any` here because this is fake and actually comes from RingRTC, which we
// can't import.
/* eslint-disable @typescript-eslint/no-explicit-any */
createCanvasVideoRenderer: () =>
({
setCanvas: noop,
enable: noop,
disable: noop,
} as any),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any,
/* eslint-enable @typescript-eslint/no-explicit-any */
hangUp: action('hang-up'),
hasLocalAudio: boolean('hasLocalAudio', overrideProps.hasLocalAudio || false),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),

View file

@ -22,7 +22,6 @@ import {
CallMode,
CallState,
GroupCallConnectionState,
CanvasVideoRenderer,
VideoFrameSource,
} from '../types/Calling';
import { ColorType } from '../types/Colors';
@ -34,7 +33,6 @@ import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
export type PropsType = {
call: DirectCallStateType | GroupCallStateType;
conversation: ConversationType;
createCanvasVideoRenderer: () => CanvasVideoRenderer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hangUp: (_: HangUpType) => void;
hasLocalAudio: boolean;
@ -62,7 +60,6 @@ export type PropsType = {
export const CallScreen: React.FC<PropsType> = ({
call,
conversation,
createCanvasVideoRenderer,
getGroupCallVideoFrameSource,
hangUp,
hasLocalAudio,
@ -174,7 +171,6 @@ export const CallScreen: React.FC<PropsType> = ({
remoteParticipantsElement = (
<GroupCallRemoteParticipants
remoteParticipants={call.remoteParticipants}
createCanvasVideoRenderer={createCanvasVideoRenderer}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
/>
);

View file

@ -46,9 +46,7 @@ const defaultCall = {
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
call: overrideProps.call || defaultCall,
conversation: overrideProps.conversation || conversation,
/* eslint-disable @typescript-eslint/no-explicit-any */
createCanvasVideoRenderer: noop as any,
/* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any,
hangUp: action('hang-up'),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),

View file

@ -6,7 +6,7 @@ import Tooltip from 'react-tooltip-lite';
import { CallingPipRemoteVideo } from './CallingPipRemoteVideo';
import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
import { CanvasVideoRenderer, VideoFrameSource } from '../types/Calling';
import { VideoFrameSource } from '../types/Calling';
import {
DirectCallStateType,
GroupCallStateType,
@ -18,7 +18,6 @@ import {
export type PropsType = {
call: DirectCallStateType | GroupCallStateType;
conversation: ConversationType;
createCanvasVideoRenderer: () => CanvasVideoRenderer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hangUp: (_: HangUpType) => void;
hasLocalVideo: boolean;
@ -36,7 +35,6 @@ const PIP_PADDING = 8;
export const CallingPip = ({
call,
conversation,
createCanvasVideoRenderer,
getGroupCallVideoFrameSource,
hangUp,
hasLocalVideo,
@ -171,7 +169,6 @@ export const CallingPip = ({
<CallingPipRemoteVideo
call={call}
conversation={conversation}
createCanvasVideoRenderer={createCanvasVideoRenderer}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
i18n={i18n}
setRendererCanvas={setRendererCanvas}

View file

@ -8,11 +8,7 @@ import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
import {
CallMode,
CanvasVideoRenderer,
VideoFrameSource,
} from '../types/Calling';
import { CallMode, VideoFrameSource } from '../types/Calling';
import {
DirectCallStateType,
GroupCallStateType,
@ -22,7 +18,6 @@ import {
export interface PropsType {
call: DirectCallStateType | GroupCallStateType;
conversation: ConversationType;
createCanvasVideoRenderer: () => CanvasVideoRenderer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType;
setRendererCanvas: (_: SetRendererCanvasType) => void;
@ -31,7 +26,6 @@ export interface PropsType {
export const CallingPipRemoteVideo = ({
call,
conversation,
createCanvasVideoRenderer,
getGroupCallVideoFrameSource,
i18n,
setRendererCanvas,
@ -87,16 +81,12 @@ export const CallingPipRemoteVideo = ({
return (
<div className="module-calling-pip__video--remote">
<GroupCallRemoteParticipant
isInPip
key={speaker.demuxId}
createCanvasVideoRenderer={createCanvasVideoRenderer}
demuxId={speaker.demuxId}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
hasRemoteAudio={speaker.hasRemoteAudio}
hasRemoteVideo={speaker.hasRemoteVideo}
height="100%"
left={0}
top={0}
width="100%"
/>
</div>
);

View file

@ -1,64 +1,147 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useRef, useEffect, CSSProperties } from 'react';
import React, {
useState,
useRef,
useMemo,
useCallback,
useEffect,
CSSProperties,
} from 'react';
import classNames from 'classnames';
import { noop } from 'lodash';
import { CanvasVideoRenderer, VideoFrameSource } from '../types/Calling';
import { VideoFrameSource } from '../types/Calling';
import { CallBackgroundBlur } from './CallBackgroundBlur';
interface PropsType {
createCanvasVideoRenderer: () => CanvasVideoRenderer;
// The max size video frame we'll support (in RGBA)
const FRAME_BUFFER_SIZE = 1920 * 1080 * 4;
interface BasePropsType {
demuxId: number;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hasRemoteAudio: boolean;
hasRemoteVideo: boolean;
height: number | string;
left: number;
top: number;
width: number | string;
}
export const GroupCallRemoteParticipant: React.FC<PropsType> = ({
createCanvasVideoRenderer,
demuxId,
getGroupCallVideoFrameSource,
hasRemoteAudio,
hasRemoteVideo,
height,
left,
top,
width,
}) => {
interface InPipPropsType {
isInPip: true;
}
interface NotInPipPropsType {
isInPip?: false;
width: number;
height: number;
left: number;
top: number;
}
type PropsType = BasePropsType & (InPipPropsType | NotInPipPropsType);
export const GroupCallRemoteParticipant: React.FC<PropsType> = props => {
const {
demuxId,
getGroupCallVideoFrameSource,
hasRemoteAudio,
hasRemoteVideo,
} = props;
const [canvasStyles, setCanvasStyles] = useState<CSSProperties>({});
const remoteVideoRef = useRef<HTMLCanvasElement | null>(null);
const canvasVideoRendererRef = useRef(createCanvasVideoRenderer());
const rafIdRef = useRef<number | null>(null);
const frameBufferRef = useRef<ArrayBuffer>(
new ArrayBuffer(FRAME_BUFFER_SIZE)
);
const videoFrameSource = useMemo(
() => getGroupCallVideoFrameSource(demuxId),
[getGroupCallVideoFrameSource, demuxId]
);
const renderVideoFrame = useCallback(() => {
const canvasEl = remoteVideoRef.current;
if (!canvasEl) {
return;
}
const context = canvasEl.getContext('2d');
if (!context) {
return;
}
const frameDimensions = videoFrameSource.receiveVideoFrame(
frameBufferRef.current
);
if (!frameDimensions) {
return;
}
const [frameWidth, frameHeight] = frameDimensions;
canvasEl.width = frameWidth;
canvasEl.height = frameHeight;
context.putImageData(
new ImageData(
new Uint8ClampedArray(
frameBufferRef.current,
0,
frameWidth * frameHeight * 4
),
frameWidth,
frameHeight
),
0,
0
);
// If our `width` and `height` props don't match the canvas's aspect ratio, we want to
// fill the container. This can happen when RingRTC gives us an inaccurate
// `videoAspectRatio`, or if the container is an unexpected size.
if (frameWidth > frameHeight) {
setCanvasStyles({ width: '100%' });
} else {
setCanvasStyles({ height: '100%' });
}
}, [videoFrameSource]);
useEffect(() => {
const canvasVideoRenderer = canvasVideoRendererRef.current;
if (hasRemoteVideo) {
canvasVideoRenderer.setCanvas(remoteVideoRef);
canvasVideoRenderer.enable(getGroupCallVideoFrameSource(demuxId));
return () => {
canvasVideoRenderer.disable();
};
if (!hasRemoteVideo) {
return noop;
}
canvasVideoRenderer.disable();
return noop;
}, [hasRemoteVideo, getGroupCallVideoFrameSource, demuxId]);
const tick = () => {
renderVideoFrame();
rafIdRef.current = requestAnimationFrame(tick);
};
// If our `width` and `height` props don't match the canvas's aspect ratio, we want to
// fill the container. This can happen when RingRTC gives us an inaccurate
// `videoAspectRatio`.
const canvasStyles: CSSProperties = {};
const canvasEl = remoteVideoRef.current;
if (hasRemoteVideo && canvasEl) {
if (canvasEl.width > canvasEl.height) {
canvasStyles.width = '100%';
} else {
canvasStyles.height = '100%';
}
rafIdRef.current = requestAnimationFrame(tick);
return () => {
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
};
}, [hasRemoteVideo, renderVideoFrame, videoFrameSource]);
let containerStyles: CSSProperties;
// TypeScript isn't smart enough to know that `isInPip` by itself disambiguates the
// types, so we have to use `props.isInPip` instead.
// eslint-disable-next-line react/destructuring-assignment
if (props.isInPip) {
containerStyles = canvasStyles;
} else {
const { top, left, width, height } = props;
containerStyles = {
height,
left,
position: 'absolute',
top,
width,
};
}
return (
@ -69,13 +152,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = ({
'module-ongoing-call__group-call-remote-participant--audio-muted': !hasRemoteAudio,
}
)}
style={{
position: 'absolute',
width,
height,
top,
left,
}}
style={containerStyles}
>
{hasRemoteVideo ? (
<canvas

View file

@ -4,7 +4,7 @@
import React, { useState, useMemo } from 'react';
import Measure from 'react-measure';
import { takeWhile, chunk, maxBy, flatten } from 'lodash';
import { CanvasVideoRenderer, VideoFrameSource } from '../types/Calling';
import { VideoFrameSource } from '../types/Calling';
import { GroupCallParticipantInfoType } from '../state/ducks/calling';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
@ -22,7 +22,6 @@ interface GridArrangement {
}
interface PropsType {
createCanvasVideoRenderer: () => CanvasVideoRenderer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
remoteParticipants: ReadonlyArray<GroupCallParticipantInfoType>;
}
@ -52,7 +51,6 @@ interface PropsType {
// screen? The biggest scalar wins as the "best arrangement".
// 4. Lay out this arrangement on the screen.
export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
createCanvasVideoRenderer,
getGroupCallVideoFrameSource,
remoteParticipants,
}) => {
@ -196,7 +194,6 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
return (
<GroupCallRemoteParticipant
key={remoteParticipant.demuxId}
createCanvasVideoRenderer={createCanvasVideoRenderer}
demuxId={remoteParticipant.demuxId}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
hasRemoteAudio={remoteParticipant.hasRemoteAudio}

View file

@ -49,13 +49,16 @@ import { getOwn } from '../util/getOwn';
import { fetchMembershipProof, getMembershipList } from '../groups';
import { missingCaseError } from '../util/missingCaseError';
const RINGRTC_SFU_URL = 'https://sfu.voip.signal.org/';
const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map<
HttpMethod,
'GET' | 'PUT' | 'POST'
'GET' | 'PUT' | 'POST' | 'DELETE'
> = new Map([
[HttpMethod.Get, 'GET'],
[HttpMethod.Put, 'PUT'],
[HttpMethod.Post, 'POST'],
[HttpMethod.Delete, 'DELETE'],
]);
export {
@ -323,67 +326,73 @@ export class CallingClass {
let isRequestingMembershipProof = false;
const outerGroupCall = RingRTC.getGroupCall(groupIdBuffer, {
onLocalDeviceStateChanged: groupCall => {
const localDeviceState = groupCall.getLocalDeviceState();
const outerGroupCall = RingRTC.getGroupCall(
groupIdBuffer,
RINGRTC_SFU_URL,
{
onLocalDeviceStateChanged: groupCall => {
const localDeviceState = groupCall.getLocalDeviceState();
if (localDeviceState.connectionState === ConnectionState.NotConnected) {
if (localDeviceState.videoMuted) {
this.disableLocalCamera();
}
if (
localDeviceState.connectionState === ConnectionState.NotConnected
) {
if (localDeviceState.videoMuted) {
this.disableLocalCamera();
}
delete this.callsByConversation[conversationId];
} else {
this.callsByConversation[conversationId] = groupCall;
if (localDeviceState.videoMuted) {
this.disableLocalCamera();
delete this.callsByConversation[conversationId];
} else {
this.enableLocalCamera();
}
}
this.callsByConversation[conversationId] = groupCall;
this.syncGroupCallToRedux(conversationId, groupCall);
},
onRemoteDeviceStatesChanged: groupCall => {
this.syncGroupCallToRedux(conversationId, groupCall);
},
onJoinedMembersChanged: groupCall => {
this.syncGroupCallToRedux(conversationId, groupCall);
},
async requestMembershipProof(groupCall) {
if (isRequestingMembershipProof) {
return;
}
isRequestingMembershipProof = true;
try {
const proof = await fetchMembershipProof({
publicParams,
secretParams,
});
if (proof) {
const proofArray = new TextEncoder().encode(proof);
groupCall.setMembershipProof(proofArray.buffer);
if (localDeviceState.videoMuted) {
this.disableLocalCamera();
} else {
this.videoCapturer.enableCaptureAndSend(groupCall);
}
}
} catch (err) {
window.log.error('Failed to fetch membership proof', err);
} finally {
isRequestingMembershipProof = false;
}
},
requestGroupMembers(groupCall) {
groupCall.setGroupMembers(
getMembershipList(conversationId).map(
member =>
new GroupMemberInfo(
uuidToArrayBuffer(member.uuid),
member.uuidCiphertext
)
)
);
},
onEnded: noop,
});
this.syncGroupCallToRedux(conversationId, groupCall);
},
onRemoteDeviceStatesChanged: groupCall => {
this.syncGroupCallToRedux(conversationId, groupCall);
},
onPeekChanged: groupCall => {
this.syncGroupCallToRedux(conversationId, groupCall);
},
async requestMembershipProof(groupCall) {
if (isRequestingMembershipProof) {
return;
}
isRequestingMembershipProof = true;
try {
const proof = await fetchMembershipProof({
publicParams,
secretParams,
});
if (proof) {
const proofArray = new TextEncoder().encode(proof);
groupCall.setMembershipProof(proofArray.buffer);
}
} catch (err) {
window.log.error('Failed to fetch membership proof', err);
} finally {
isRequestingMembershipProof = false;
}
},
requestGroupMembers(groupCall) {
groupCall.setGroupMembers(
getMembershipList(conversationId).map(
member =>
new GroupMemberInfo(
uuidToArrayBuffer(member.uuid),
member.uuidCiphertext
)
)
);
},
onEnded: noop,
}
);
if (!outerGroupCall) {
// This should be very rare, likely due to RingRTC not being able to get a lock

View file

@ -3,7 +3,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { CanvasVideoRenderer } from 'ringrtc';
import { mapDispatchToProps } from '../actions';
import { CallManager } from '../../components/CallManager';
import { calling as callingService } from '../../services/calling';
@ -21,8 +20,6 @@ function renderDeviceSelection(): JSX.Element {
return <SmartCallingDeviceSelection />;
}
const createCanvasVideoRenderer = () => new CanvasVideoRenderer();
const getGroupCallVideoFrameSource = callingService.getGroupCallVideoFrameSource.bind(
callingService
);
@ -108,7 +105,6 @@ const mapStateToIncomingCallProp = (state: StateType) => {
const mapStateToProps = (state: StateType) => ({
activeCall: mapStateToActiveCallProp(state),
availableCameras: state.calling.availableCameras,
createCanvasVideoRenderer,
getGroupCallVideoFrameSource,
i18n: getIntl(state),
incomingCall: mapStateToIncomingCallProp(state),

View file

@ -69,16 +69,6 @@ export interface GroupCallRemoteParticipantType {
title: string;
}
// Should match RingRTC's CanvasVideoRenderer
interface Ref<T> {
readonly current: T | null;
}
export interface CanvasVideoRenderer {
setCanvas(canvas: Ref<HTMLCanvasElement> | undefined): void;
enable(source: VideoFrameSource): void;
disable(): void;
}
// Should match RingRTC's VideoFrameSource
export interface VideoFrameSource {
receiveVideoFrame(buffer: ArrayBuffer): [number, number] | undefined;

View file

@ -14427,7 +14427,7 @@
"rule": "React-useRef",
"path": "ts/components/CallingPip.tsx",
"line": " const videoContainerRef = React.useRef(null);",
"lineNumber": 48,
"lineNumber": 46,
"reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Element is measured. Its HTML is not used."
@ -14436,7 +14436,7 @@
"rule": "React-useRef",
"path": "ts/components/CallingPip.tsx",
"line": " const localVideoRef = React.useRef(null);",
"lineNumber": 49,
"lineNumber": 47,
"reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering."
@ -14571,7 +14571,7 @@
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const remoteVideoRef = react_1.useRef(null);",
"lineNumber": 20,
"lineNumber": 24,
"reasonCategory": "usageTrusted",
"updated": "2020-11-11T21:56:04.179Z",
"reasonDetail": "Needed to render the remote video element."
@ -14579,19 +14579,19 @@
{
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const canvasVideoRendererRef = react_1.useRef(createCanvasVideoRenderer());",
"lineNumber": 21,
"line": " const rafIdRef = react_1.useRef(null);",
"lineNumber": 25,
"reasonCategory": "usageTrusted",
"updated": "2020-11-11T21:56:04.179Z",
"updated": "2020-11-17T16:24:25.480Z",
"reasonDetail": "Doesn't touch the DOM."
},
{
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.tsx",
"line": " const canvasVideoRendererRef = useRef(createCanvasVideoRenderer());",
"lineNumber": 34,
"path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const frameBufferRef = react_1.useRef(new ArrayBuffer(FRAME_BUFFER_SIZE));",
"lineNumber": 26,
"reasonCategory": "usageTrusted",
"updated": "2020-11-11T21:56:04.179Z",
"updated": "2020-11-17T16:24:25.480Z",
"reasonDetail": "Doesn't touch the DOM."
},
{

View file

@ -14300,9 +14300,9 @@ rimraf@~2.4.0:
dependencies:
glob "^6.0.1"
"ringrtc@https://github.com/signalapp/signal-ringrtc-node.git#7de95cffa71019e94c74fc43313c28eaf23c66e1":
"ringrtc@https://github.com/signalapp/signal-ringrtc-node.git#65fbea67295005fe1a9db2cbe85fce0d5297848d":
version "2.8.1"
resolved "https://github.com/signalapp/signal-ringrtc-node.git#7de95cffa71019e94c74fc43313c28eaf23c66e1"
resolved "https://github.com/signalapp/signal-ringrtc-node.git#65fbea67295005fe1a9db2cbe85fce0d5297848d"
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.1"