Fix microphone permission checking for audio recording

See [#5580][0].

[0]: https://github.com/signalapp/Signal-Desktop/pull/5580
This commit is contained in:
David Sanders 2021-10-27 11:16:09 -05:00 committed by Evan Hahn
parent 1dc353f089
commit 79b3b6408e
8 changed files with 69 additions and 43 deletions

View File

@ -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',

View File

@ -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);

View File

@ -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');
@ -120,8 +127,15 @@ export const AudioCapture = ({
return () => {
clearInterval(interval);
closeToast();
};
}, [completeRecording, errorRecording, isRecording, setDurationText]);
}, [
closeToast,
completeRecording,
errorRecording,
isRecording,
setDurationText,
]);
const clickCancel = useCallback(() => {
cancelRecording();
@ -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} />;
@ -226,8 +236,6 @@ export const AudioCapture = ({
if (draftAttachments.length) {
setToastType(ToastType.VoiceNoteMustBeOnlyAttachment);
} else {
setDurationText(START_DURATION_TEXT);
setToastType(ToastType.VoiceNoteLimit);
startRecording();
}
}}

View File

@ -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';
@ -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();
@ -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);
@ -81,7 +90,10 @@ export class RecorderClass {
if (this.recorder) {
this.recorder.startRecording();
return true;
}
return false;
}
async stop(): Promise<Blob | undefined> {
@ -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 {

View File

@ -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 {
@ -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();

View File

@ -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,
@ -87,10 +89,12 @@ function startRecording(): ThunkAction<
return;
}
dispatch({
type: START_RECORDING,
payload: undefined,
});
if (recordingStarted) {
dispatch({
type: START_RECORDING,
payload: undefined,
});
}
};
}

View File

@ -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
ts/window.d.ts vendored
View File

@ -160,7 +160,7 @@ declare global {
QRCode: any;
removeSetupMenuItems: () => unknown;
showPermissionsPopup: () => unknown;
showPermissionsPopup: () => Promise<void>;
FontFace: typeof FontFace;
_: typeof Underscore;