Skip to content

Commit

Permalink
Fix microphone permission checking for audio recording
Browse files Browse the repository at this point in the history
See [#5580][0].

[0]: #5580
  • Loading branch information
dsanders11 authored and EvanHahn-Signal committed Oct 27, 2021
1 parent 1dc353f commit 79b3b64
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 43 deletions.
11 changes: 9 additions & 2 deletions app/main.ts
Expand Up @@ -1862,8 +1862,15 @@ ipc.on(

// Permissions Popup-related IPC calls

ipc.on('show-permissions-popup', () => {
showPermissionsPopupWindow(false, false);
ipc.handle('show-permissions-popup', async () => {
try {
await showPermissionsPopupWindow(false, false);
} catch (error) {
getLogger().error(
'show-permissions-popup error:',
error && error.stack ? error.stack : error
);
}
});
ipc.handle(
'show-calling-permissions-popup',
Expand Down
2 changes: 1 addition & 1 deletion preload.js
Expand Up @@ -239,7 +239,7 @@ try {
// Settings-related events

window.showSettings = () => ipc.send('show-settings');
window.showPermissionsPopup = () => ipc.send('show-permissions-popup');
window.showPermissionsPopup = () => ipc.invoke('show-permissions-popup');
window.showCallingPermissionsPopup = forCamera =>
ipc.invoke('show-calling-permissions-popup', forCamera);

Expand Down
22 changes: 15 additions & 7 deletions ts/components/conversation/AudioCapture.tsx
Expand Up @@ -97,12 +97,19 @@ export const AudioCapture = ({
const startRecordingShortcut = useStartRecordingShortcut(startRecording);
useKeyboardShortcuts(startRecordingShortcut);

const closeToast = useCallback(() => {
setToastType(undefined);
}, []);

// Update timestamp regularly, then timeout if recording goes over five minutes
useEffect(() => {
if (!isRecording) {
return;
}

setDurationText(START_DURATION_TEXT);
setToastType(ToastType.VoiceNoteLimit);

const startTime = Date.now();
const interval = setInterval(() => {
const duration = moment.duration(Date.now() - startTime, 'ms');
Expand All @@ -120,8 +127,15 @@ export const AudioCapture = ({

return () => {
clearInterval(interval);
closeToast();
};
}, [completeRecording, errorRecording, isRecording, setDurationText]);
}, [
closeToast,
completeRecording,
errorRecording,
isRecording,
setDurationText,
]);

const clickCancel = useCallback(() => {
cancelRecording();
Expand All @@ -131,10 +145,6 @@ export const AudioCapture = ({
completeRecording(conversationId, onSendAudioRecording);
}, [conversationId, completeRecording, onSendAudioRecording]);

const closeToast = useCallback(() => {
setToastType(undefined);
}, []);

let toastElement: JSX.Element | undefined;
if (toastType === ToastType.VoiceNoteLimit) {
toastElement = <ToastVoiceNoteLimit i18n={i18n} onClose={closeToast} />;
Expand Down Expand Up @@ -226,8 +236,6 @@ export const AudioCapture = ({
if (draftAttachments.length) {
setToastType(ToastType.VoiceNoteMustBeOnlyAttachment);
} else {
setDurationText(START_DURATION_TEXT);
setToastType(ToastType.VoiceNoteLimit);
startRecording();
}
}}
Expand Down
32 changes: 18 additions & 14 deletions ts/services/audioRecorder.ts
@@ -1,6 +1,7 @@
// Copyright 2016-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions';
import * as log from '../logging/log';
import type { WebAudioRecorderClass } from '../window.d';

Expand Down Expand Up @@ -42,7 +43,15 @@ export class RecorderClass {
}
}

async start(): Promise<void> {
async start(): Promise<boolean> {
const hasMicrophonePermission = await requestMicrophonePermissions();
if (!hasMicrophonePermission) {
log.info(
'Recorder/start: Microphone permission was denied, new audio recording not allowed.'
);
return false;
}

this.clear();

this.context = new AudioContext();
Expand All @@ -61,11 +70,11 @@ export class RecorderClass {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
if (!this.context || !this.input) {
this.onError(
this.recorder,
new Error('Recorder/getUserMedia/stream: Missing context or input!')
const err = new Error(
'Recorder/getUserMedia/stream: Missing context or input!'
);
return;
this.onError(this.recorder, err);
throw err;
}
this.source = this.context.createMediaStreamSource(stream);
this.source.connect(this.input);
Expand All @@ -81,7 +90,10 @@ export class RecorderClass {

if (this.recorder) {
this.recorder.startRecording();
return true;
}

return false;
}

async stop(): Promise<Blob | undefined> {
Expand Down Expand Up @@ -120,15 +132,7 @@ export class RecorderClass {

this.clear();

if (error && error.name === 'NotAllowedError') {
log.warn('Recorder/onError: Microphone permission missing');
window.showPermissionsPopup();
} else {
log.error(
'Recorder/onError:',
error && error.stack ? error.stack : error
);
}
log.error('Recorder/onError:', error && error.stack ? error.stack : error);
}

getBlob(): Blob {
Expand Down
15 changes: 2 additions & 13 deletions ts/services/calling.ts
Expand Up @@ -92,6 +92,7 @@ import {
} from '../calling/constants';
import { callingMessageToProto } from '../util/callingMessageToProto';
import { getSendOptions } from '../util/getSendOptions';
import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions';
import { SignalService as Proto } from '../protobuf';
import dataInterface from '../sql/Client';
import {
Expand Down Expand Up @@ -1510,20 +1511,8 @@ export class CallingClass {
return true;
}

private async requestMicrophonePermissions(): Promise<boolean> {
const microphonePermission = await window.getMediaPermissions();
if (!microphonePermission) {
await window.showCallingPermissionsPopup(false);

// Check the setting again (from the source of truth).
return window.getMediaPermissions();
}

return true;
}

private async requestPermissions(isVideoCall: boolean): Promise<boolean> {
const microphonePermission = await this.requestMicrophonePermissions();
const microphonePermission = await requestMicrophonePermissions();
if (microphonePermission) {
if (isVideoCall) {
return this.requestCameraPermissions();
Expand Down
14 changes: 9 additions & 5 deletions ts/state/ducks/audioRecorder.ts
Expand Up @@ -77,8 +77,10 @@ function startRecording(): ThunkAction<
return;
}

let recordingStarted = false;

try {
await recorder.start();
recordingStarted = await recorder.start();
} catch (err) {
dispatch({
type: ERROR_RECORDING,
Expand All @@ -87,10 +89,12 @@ function startRecording(): ThunkAction<
return;
}

dispatch({
type: START_RECORDING,
payload: undefined,
});
if (recordingStarted) {
dispatch({
type: START_RECORDING,
payload: undefined,
});
}
};
}

Expand Down
14 changes: 14 additions & 0 deletions ts/util/requestMicrophonePermissions.ts
@@ -0,0 +1,14 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

export async function requestMicrophonePermissions(): Promise<boolean> {
const microphonePermission = await window.getMediaPermissions();
if (!microphonePermission) {
await window.showCallingPermissionsPopup(false);

// Check the setting again (from the source of truth).
return window.getMediaPermissions();
}

return true;
}
2 changes: 1 addition & 1 deletion ts/window.d.ts
Expand Up @@ -160,7 +160,7 @@ declare global {

QRCode: any;
removeSetupMenuItems: () => unknown;
showPermissionsPopup: () => unknown;
showPermissionsPopup: () => Promise<void>;

FontFace: typeof FontFace;
_: typeof Underscore;
Expand Down

0 comments on commit 79b3b64

Please sign in to comment.