Browse Source

feat(call): check consent before joining the call and send to server for users (MVP)

Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
pull/10633/head
Maksim Sukharev 2 years ago
parent
commit
32e0d806fa
No known key found for this signature in database GPG Key ID: 6349D071889BD1D5
  1. 57
      src/components/MediaSettings/MediaSettings.vue
  2. 17
      src/components/TopBar/CallButton.vue
  3. 5
      src/services/callsService.js
  4. 4
      src/store/participantsStore.js
  5. 26
      src/utils/signaling.js
  6. 12
      src/utils/webrtc/index.js

57
src/components/MediaSettings/MediaSettings.vue

@ -132,8 +132,23 @@
</NcCheckboxRadioSwitch>
<!-- Recording warning -->
<NcNoteCard v-else-if="isStartingRecording || isRecording" type="warning">
<p>{{ t('spreed', 'The call is being recorded.') }}</p>
<NcNoteCard v-if="showRecordingWarning" type="warning">
<p v-if="isStartingRecording || isRecording">
<strong>{{ t('spreed', 'The call is being recorded.') }}</strong>
</p>
<p v-else>
<strong>{{ t('spreed', 'The call might be recorded.') }}</strong>
</p>
<template v-if="isRecordingConsentRequired">
<p>
{{ t('spreed', 'The recording might include your voice, video from camera, and screen share. Your consent is required before joining the call.') }}
</p>
<NcCheckboxRadioSwitch class="checkbox--warning"
:checked="recordingConsentGiven"
@update:checked="setRecordingConsentGiven">
{{ t('spreed', 'Give consent to the recording of this call') }}
</NcCheckboxRadioSwitch>
</template>
</NcNoteCard>
<!-- buttons bar at the bottom -->
@ -169,7 +184,9 @@
<CallButton v-if="!isInCall"
class="call-button"
is-media-settings
:is-recording-from-start.sync="isRecordingFromStart"
:is-recording-from-start="isRecordingFromStart"
:disabled="isRecordingConsentRequired && !recordingConsentGiven"
:recording-consent-given="recordingConsentGiven"
:silent-call="silentCall" />
<NcButton v-else-if="showUpdateChangesButton" @click="closeModalAndApplySettings">
{{ t('spreed', 'Apply settings') }}
@ -214,6 +231,7 @@ import { useGuestNameStore } from '../../stores/guestName.js'
import { localMediaModel } from '../../utils/webrtc/index.js'
const recordingEnabled = getCapabilities()?.spreed?.config?.call?.recording || false
const recordingConsent = getCapabilities()?.spreed?.config?.call?.['recording-consent']
export default {
name: 'MediaSettings',
@ -245,6 +263,15 @@ export default {
mixins: [devices, isInLobby],
props: {
recordingConsentGiven: {
type: Boolean,
default: false
}
},
emits: ['update:recording-consent-given'],
setup() {
const isInCall = useIsInCall()
const guestNameStore = useGuestNameStore()
@ -341,6 +368,15 @@ export default {
return this.canFullModerate && recordingEnabled
},
isRecordingConsentRequired() {
return recordingConsent === CALL.RECORDING_CONSENT.REQUIRED
|| (recordingConsent === CALL.RECORDING_CONSENT.OPTIONAL && this.conversation.recordingConsent === CALL.RECORDING_CONSENT.REQUIRED)
},
showRecordingWarning() {
return !this.isInCall && (this.isStartingRecording || this.isRecording || this.isRecordingConsentRequired)
},
showSilentCallOption() {
return !(this.hasCall && !this.isInLobby)
},
@ -404,6 +440,10 @@ export default {
this.toggleVideo()
}
},
isRecordingFromStart(value) {
this.setRecordingConsentGiven(value)
},
},
mounted() {
@ -565,6 +605,10 @@ export default {
this.tabContent = 'none'
}
},
setRecordingConsentGiven(value) {
this.$emit('update:recording-consent-given', value)
}
},
}
</script>
@ -653,6 +697,13 @@ export default {
display: flex;
justify-content: center;
margin: calc(var(--default-grid-baseline) * 2);
&--warning {
&:focus-within :deep(.checkbox-radio-switch__label),
& :deep(.checkbox-radio-switch__label:hover) {
background-color: var(--note-background) !important;
}
}
}
:deep(.modal-container) {

17
src/components/TopBar/CallButton.vue

@ -127,6 +127,11 @@ export default {
],
props: {
disabled: {
type: Boolean,
default: false,
},
/**
* Whether the component is used in MediaSettings or not
* (when click will directly start a call)
@ -148,7 +153,12 @@ export default {
isRecordingFromStart: {
type: Boolean,
default: false,
}
},
recordingConsentGiven: {
type: Boolean,
default: false,
},
},
setup() {
@ -203,8 +213,8 @@ export default {
},
startCallButtonDisabled() {
return (!this.conversation.canStartCall
&& !this.hasCall)
return this.disabled
|| (!this.conversation.canStartCall && !this.hasCall)
|| this.isInLobby
|| this.conversation.readOnly
|| this.isNextcloudTalkHashDirty
@ -311,6 +321,7 @@ export default {
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
flags,
silent: this.hasCall ? true : this.silentCall,
recordingConsent: this.recordingConsentGiven,
})
this.loading = false

5
src/services/callsService.js

@ -43,11 +43,12 @@ import {
* @param {number} flags The available PARTICIPANT.CALL_FLAG for this participants
* @param {boolean} silent Whether the call should trigger a notifications and
* sound for other participants or not
* @param {boolean} recordingConsent Whether the participant gave his consent to be recorded
* @return {number} The actual flags based on the available media
*/
const joinCall = async function(token, flags, silent) {
const joinCall = async function(token, flags, silent, recordingConsent) {
try {
return await signalingJoinCall(token, flags, silent)
return await signalingJoinCall(token, flags, silent, recordingConsent)
} catch (error) {
console.debug('Error while joining call: ', error)
}

4
src/store/participantsStore.js

@ -627,7 +627,7 @@ const actions = {
return false
},
async joinCall({ commit, getters }, { token, participantIdentifier, flags, silent }) {
async joinCall({ commit, getters }, { token, participantIdentifier, flags, silent, recordingConsent }) {
if (!participantIdentifier?.sessionId) {
console.error('Trying to join call without sessionId')
return
@ -645,7 +645,7 @@ const actions = {
flags,
})
const actualFlags = await joinCall(token, flags, silent)
const actualFlags = await joinCall(token, flags, silent, recordingConsent)
const updatedData = {
inCall: actualFlags,

26
src/utils/signaling.js

@ -84,6 +84,7 @@ function Base(settings) {
this.currentCallToken = null
this.currentCallFlags = null
this.currentCallSilent = null
this.currentCallRecordingConsent = null
this.nextcloudSessionId = null
this.handlers = {}
this.features = {}
@ -174,6 +175,7 @@ Signaling.Base.prototype._resetCurrentCallParameters = function() {
this.currentCallToken = null
this.currentCallFlags = null
this.currentCallSilent = null
this.currentCallRecordingConsent = null
}
Signaling.Base.prototype.disconnect = function() {
@ -244,7 +246,7 @@ Signaling.Base.prototype.joinRoom = function(token, sessionId) {
resolve()
if (this.currentCallToken === token) {
// We were in this call before, join again.
this.joinCall(token, this.currentCallFlags, this.currentCallSilent)
this.joinCall(token, this.currentCallFlags, this.currentCallSilent, this.currentCallRecordingConsent)
} else {
this._resetCurrentCallParameters()
}
@ -286,18 +288,20 @@ Signaling.Base.prototype._joinCallSuccess = function(/* token */) {
// Override in subclasses if necessary.
}
Signaling.Base.prototype.joinCall = function(token, flags, silent) {
Signaling.Base.prototype.joinCall = function(token, flags, silent, recordingConsent) {
return new Promise((resolve, reject) => {
this._trigger('beforeJoinCall', [token])
axios.post(generateOcsUrl('apps/spreed/api/v4/call/{token}', { token }), {
flags,
silent,
recordingConsent,
})
.then(function() {
this.currentCallToken = token
this.currentCallFlags = flags
this.currentCallSilent = silent
this.currentCallRecordingConsent = recordingConsent
this._trigger('joinCall', [token])
resolve()
this._joinCallSuccess(token)
@ -523,7 +527,7 @@ Signaling.Internal.prototype._startPullingMessages = function() {
localParticipant = message.data.find(participant => participant.sessionId === this.sessionId)
if (this._joinCallAgainOnceDisconnected && !localParticipant.inCall) {
this._joinCallAgainOnceDisconnected = false
this.joinCall(this.currentCallToken, this.currentCallFlags, this.currentCallSilent)
this.joinCall(this.currentCallToken, this.currentCallFlags, this.currentCallSilent, this.currentCallRecordingConsent)
}
break
@ -1184,7 +1188,7 @@ Signaling.Standalone.prototype._joinRoomSuccess = function(token, nextcloudSessi
}.bind(this))
}
Signaling.Standalone.prototype.joinCall = function(token, flags, silent) {
Signaling.Standalone.prototype.joinCall = function(token, flags, silent, recordingConsent) {
if (this.signalingRoomJoined !== token) {
console.debug('Not joined room yet, not joining call', token)
@ -1199,6 +1203,7 @@ Signaling.Standalone.prototype.joinCall = function(token, flags, silent) {
token,
flags,
silent,
recordingConsent,
resolve,
reject,
}
@ -1219,6 +1224,7 @@ Signaling.Standalone.prototype.joinCall = function(token, flags, silent) {
this.currentCallToken = token
this.currentCallFlags = flags
this.currentCallSilent = silent
this.currentCallRecordingConsent = recordingConsent
this._trigger('joinCall', [token])
resolve()
@ -1235,11 +1241,13 @@ Signaling.Standalone.prototype.joinResponseReceived = function(data, token) {
const pendingJoinCallResolve = this.pendingJoinCall.resolve
const pendingJoinCallReject = this.pendingJoinCall.reject
this.joinCall(this.pendingJoinCall.token, this.pendingJoinCall.flags, this.pendingJoinCall.silent).then(() => {
pendingJoinCallResolve()
}).catch(error => {
pendingJoinCallReject(error)
})
const { flags, silent, recordingConsent } = this.pendingJoinCall
this.joinCall(token, flags, silent, recordingConsent)
.then(() => {
pendingJoinCallResolve()
}).catch(error => {
pendingJoinCallReject(error)
})
this.pendingJoinCall = null
}

12
src/utils/webrtc/index.js

@ -152,8 +152,9 @@ let failedToStartCall = null
* @param {object} configuration Media to connect with
* @param {boolean} silent Whether the call should trigger a notifications and
* sound for other participants or not
* @param {boolean} recordingConsent Whether the participant gave his consent to be recorded
*/
function startCall(signaling, configuration, silent) {
function startCall(signaling, configuration, silent, recordingConsent) {
let flags = PARTICIPANT.CALL_FLAG.IN_CALL
if (configuration) {
if (configuration.audio) {
@ -164,7 +165,7 @@ function startCall(signaling, configuration, silent) {
}
}
signaling.joinCall(pendingJoinCallToken, flags, silent).then(() => {
signaling.joinCall(pendingJoinCallToken, flags, silent, recordingConsent).then(() => {
startedCall(flags)
}).catch(error => {
failedToStartCall(error)
@ -209,10 +210,11 @@ async function signalingJoinConversation(token, sessionId) {
* @param {number} flags Bitwise combination of PARTICIPANT.CALL_FLAG
* @param {boolean} silent Whether the call should trigger a notifications and
* sound for other participants or not
* @param {boolean} recordingConsent Whether the participant gave his consent to be recorded
* @return {Promise<void>} Resolved with the actual flags based on the
* available media
*/
async function signalingJoinCall(token, flags, silent) {
async function signalingJoinCall(token, flags, silent, recordingConsent) {
if (tokensInSignaling[token]) {
pendingJoinCallToken = token
@ -269,13 +271,13 @@ async function signalingJoinCall(token, flags, silent) {
webRtc.off('localMediaStarted', startCallOnceLocalMediaStarted)
webRtc.off('localMediaError', startCallOnceLocalMediaError)
startCall(_signaling, configuration, silent)
startCall(_signaling, configuration, silent, recordingConsent)
}
const startCallOnceLocalMediaError = () => {
webRtc.off('localMediaStarted', startCallOnceLocalMediaStarted)
webRtc.off('localMediaError', startCallOnceLocalMediaError)
startCall(_signaling, null, silent)
startCall(_signaling, null, silent, recordingConsent)
}
// ".once" can not be used, as both handlers need to be removed when

Loading…
Cancel
Save