diff --git a/addons/mail/static/src/models/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler.js index c05963dd875f17ad0ae61bb0667751d6b18567f5..a799d9d276beb27ae766db84323e6c78a59e09c9 100644 --- a/addons/mail/static/src/models/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler.js @@ -422,7 +422,7 @@ registerModel({ /** * @private * @param {Object} data - * @param {string} data.sender + * @param {number} data.sender * @param {string[]} data.notifications */ _handleNotificationRtcPeerToPeer({ sender, notifications }) { diff --git a/addons/mail/static/src/models/rtc.js b/addons/mail/static/src/models/rtc.js index c40e9d4f21873db5eb98052e7254998f42165c94..1577fec602140925ea81be4aeea56960fec9e33e 100644 --- a/addons/mail/static/src/models/rtc.js +++ b/addons/mail/static/src/models/rtc.js @@ -36,7 +36,7 @@ registerModel({ */ this._dataChannels = {}; /** - * Set of peerTokens, used to track which calls are outgoing, + * Set of RtcSession.ids, used to track which calls are outgoing, * which is used when attempting to recover a failed peer connection by * inverting the call direction. */ @@ -128,7 +128,7 @@ registerModel({ } }, /** - * @param {number|String} sender id of the session that sent the notification + * @param {number} sender id of the session that sent the notification * @param {String} content JSON */ async handleNotification(sender, content) { @@ -147,18 +147,18 @@ registerModel({ switch (event) { case "offer": this._addLogEntry(sender, `received notification: ${event}`, { step: 'received offer' }); - await this._handleRtcTransactionOffer(rtcSession.peerToken, payload); + await this._handleRtcTransactionOffer(rtcSession, payload); break; case "answer": this._addLogEntry(sender, `received notification: ${event}`, { step: 'received answer' }); - await this._handleRtcTransactionAnswer(rtcSession.peerToken, payload); + await this._handleRtcTransactionAnswer(rtcSession, payload); break; case "ice-candidate": - await this._handleRtcTransactionICECandidate(rtcSession.peerToken, payload); + await this._handleRtcTransactionICECandidate(rtcSession, payload); break; case "disconnect": this._addLogEntry(sender, `received notification: ${event}`, { step: ' peer cleanly disconnected ' }); - this._removePeer(rtcSession.peerToken); + this._removePeer(rtcSession.id); break; case 'trackChange': this._handleTrackChange(rtcSession, payload); @@ -202,7 +202,7 @@ registerModel({ if (this._peerConnections) { const peerTokens = Object.keys(this._peerConnections); for (const token of peerTokens) { - this._removePeer(token); + this._removePeer(Number(token)); } } @@ -312,7 +312,7 @@ registerModel({ this.update({ audioTrack }); await this.async(() => this.updateVoiceActivation()); for (const [token, peerConnection] of Object.entries(this._peerConnections)) { - await this._updateRemoteTrack(peerConnection, 'audio', { token }); + await this._updateRemoteTrack(peerConnection, 'audio', { sessionId: Number(token) }); } } }, @@ -396,14 +396,14 @@ registerModel({ }, /** * @private - * @param {String} token + * @param {RtcSession} rtcSession */ - async _callPeer(token) { - const peerConnection = this._createPeerConnection(token); + async _callPeer(rtcSession) { + const peerConnection = this._createPeerConnection(rtcSession); for (const trackKind of TRANSCEIVER_ORDER) { - await this._updateRemoteTrack(peerConnection, trackKind, { initTransceiver: true, token }); + await this._updateRemoteTrack(peerConnection, trackKind, { initTransceiver: true, sessionId: rtcSession.id }); } - this._outGoingCallTokens.add(token); + this._outGoingCallTokens.add(rtcSession.id); }, /** * Call all the sessions that do not have an already initialized peerConnection. @@ -414,18 +414,18 @@ registerModel({ if (!this.channel.rtcSessions) { return; } - for (const session of this.channel.rtcSessions) { - if (session.peerToken in this._peerConnections) { + for (const rtcSession of this.channel.rtcSessions) { + if (rtcSession.peerToken in this._peerConnections) { continue; } - if (session === this.currentRtcSession) { + if (rtcSession === this.currentRtcSession) { continue; } - session.update({ + rtcSession.update({ connectionState: 'Not connected: sending initial RTC offer', }); - this._addLogEntry(session.peerToken, 'init call', { step: 'init call' }); - this._callPeer(session.peerToken); + this._addLogEntry(rtcSession.id, 'init call', { step: 'init call' }); + this._callPeer(rtcSession); } }, /** @@ -439,29 +439,29 @@ registerModel({ * Creates and setup a RTCPeerConnection. * * @private - * @param {String} token + * @param {RtcSession} rtcSession */ - _createPeerConnection(token) { + _createPeerConnection(rtcSession) { const peerConnection = new window.RTCPeerConnection({ iceServers: this.iceServers }); - this._addLogEntry(token, `RTCPeerConnection created`, { step: 'peer connection created' }); + this._addLogEntry(rtcSession.id, `RTCPeerConnection created`, { step: 'peer connection created' }); peerConnection.onicecandidate = async (event) => { if (!event.candidate) { return; } - await this._notifyPeers([token], { + await this._notifyPeers([rtcSession.id], { event: 'ice-candidate', payload: { candidate: event.candidate }, }); }; peerConnection.oniceconnectionstatechange = (event) => { - this._onICEConnectionStateChange(peerConnection.iceConnectionState, token); + this._onICEConnectionStateChange(peerConnection.iceConnectionState, rtcSession); }; peerConnection.onconnectionstatechange = (event) => { - this._onConnectionStateChange(peerConnection.connectionState, token); + this._onConnectionStateChange(peerConnection.connectionState, rtcSession); }; peerConnection.onicecandidateerror = async (error) => { - this._addLogEntry(token, `ice candidate error`); - this._recoverConnection(token, { delay: this.recoveryTimeout, reason: 'ice candidate error' }); + this._addLogEntry(rtcSession.id, `ice candidate error`); + this._recoverConnection(rtcSession, { delay: this.recoveryTimeout, reason: 'ice candidate error' }); }; peerConnection.onnegotiationneeded = async (event) => { const offer = await peerConnection.createOffer(); @@ -469,22 +469,22 @@ registerModel({ await peerConnection.setLocalDescription(offer); } catch (e) { // Possibly already have a remote offer here: cannot set local description - this._addLogEntry(token, `couldn't setLocalDescription`, { error: e }); + this._addLogEntry(rtcSession.id, `couldn't setLocalDescription`, { error: e }); return; } - this._addLogEntry(token, `sending notification: offer`, { step: 'sending offer' }); - await this._notifyPeers([token], { + this._addLogEntry(rtcSession.id, `sending notification: offer`, { step: 'sending offer' }); + await this._notifyPeers([rtcSession.id], { event: 'offer', payload: { sdp: peerConnection.localDescription }, }); }; peerConnection.ontrack = ({ transceiver, track }) => { - this._addLogEntry(token, `received ${track.kind} track`); - this._updateExternalSessionTrack(track, token); + this._addLogEntry(rtcSession.id, `received ${track.kind} track`); + this._updateExternalSessionTrack(track, rtcSession); }; const dataChannel = peerConnection.createDataChannel("notifications", { negotiated: true, id: 1 }); dataChannel.onmessage = (event) => { - this.handleNotification(token, event.data); + this.handleNotification(rtcSession.id, event.data); }; dataChannel.onopen = async () => { /** @@ -492,7 +492,7 @@ registerModel({ * even when it is disabled on the sender-side. */ try { - await this._notifyPeers([token], { + await this._notifyPeers([rtcSession.id], { event: 'trackChange', type: 'peerToPeer', payload: { @@ -507,11 +507,11 @@ registerModel({ if (!(e instanceof DOMException) || e.name !== "OperationError") { throw e; } - this._addLogEntry(token, `failed to send on datachannel; dataChannelInfo: ${this._serializeRTCDataChannel(dataChannel)}`, { error: e }); + this._addLogEntry(rtcSession.id, `failed to send on datachannel; dataChannelInfo: ${this._serializeRTCDataChannel(dataChannel)}`, { error: e }); } }; - this._peerConnections[token] = peerConnection; - this._dataChannels[token] = dataChannel; + this._peerConnections[rtcSession.id] = peerConnection; + this._dataChannels[rtcSession.id] = dataChannel; return peerConnection; }, /** @@ -526,12 +526,12 @@ registerModel({ }, /** * @private - * @param {String} fromToken + * @param {RtcSession} rtcSession * @param {Object} param1 * @param {Object} param1.sdp Session Description Protocol */ - async _handleRtcTransactionAnswer(fromToken, { sdp }) { - const peerConnection = this._peerConnections[fromToken]; + async _handleRtcTransactionAnswer(rtcSession, { sdp }) { + const peerConnection = this._peerConnections[rtcSession.id]; if (!peerConnection || this.invalidIceConnectionStates.has(peerConnection.iceConnectionState) || peerConnection.signalingState === 'stable') { return; } @@ -543,18 +543,18 @@ registerModel({ try { await peerConnection.setRemoteDescription(rtcSessionDescription); } catch (e) { - this._addLogEntry(fromToken, 'answer handling: Failed at setting remoteDescription', { error: e }); + this._addLogEntry(rtcSession.id, 'answer handling: Failed at setting remoteDescription', { error: e }); // ignored the transaction may have been resolved by another concurrent offer. } }, /** * @private - * @param {String} fromToken + * @param {RtcSession} rtcSession * @param {Object} param1 * @param {Object} param1.candidate RTCIceCandidateInit */ - async _handleRtcTransactionICECandidate(fromToken, { candidate }) { - const peerConnection = this._peerConnections[fromToken]; + async _handleRtcTransactionICECandidate(rtcSession, { candidate }) { + const peerConnection = this._peerConnections[rtcSession.id]; if (!peerConnection || this.invalidIceConnectionStates.has(peerConnection.iceConnectionState)) { return; } @@ -562,18 +562,18 @@ registerModel({ try { await peerConnection.addIceCandidate(rtcIceCandidate); } catch (error) { - this._addLogEntry(fromToken, 'ICE candidate handling: failed at adding the candidate to the connection', { error }); - this._recoverConnection(fromToken, { delay: this.recoveryTimeout, reason: 'failed at adding ice candidate' }); + this._addLogEntry(rtcSession.id, 'ICE candidate handling: failed at adding the candidate to the connection', { error }); + this._recoverConnection(rtcSession, { delay: this.recoveryTimeout, reason: 'failed at adding ice candidate' }); } }, /** * @private - * @param {String} fromToken + * @param {RtcSession} rtcSession * @param {Object} param1 * @param {Object} param1.sdp Session Description Protocol */ - async _handleRtcTransactionOffer(fromToken, { sdp }) { - const peerConnection = this._peerConnections[fromToken] || this._createPeerConnection(fromToken); + async _handleRtcTransactionOffer(rtcSession, { sdp }) { + const peerConnection = this._peerConnections[rtcSession.id] || this._createPeerConnection(rtcSession); if (!peerConnection || this.invalidIceConnectionStates.has(peerConnection.iceConnectionState)) { return; @@ -586,32 +586,32 @@ registerModel({ try { await peerConnection.setRemoteDescription(rtcSessionDescription); } catch (e) { - this._addLogEntry(fromToken, 'offer handling: failed at setting remoteDescription', { error: e }); + this._addLogEntry(rtcSession.id, 'offer handling: failed at setting remoteDescription', { error: e }); return; } - await this._updateRemoteTrack(peerConnection, 'audio', { token: fromToken }); - await this._updateRemoteTrack(peerConnection, 'video', { token: fromToken }); + await this._updateRemoteTrack(peerConnection, 'audio', { sessionId: rtcSession.id }); + await this._updateRemoteTrack(peerConnection, 'video', { sessionId: rtcSession.id }); let answer; try { answer = await peerConnection.createAnswer(); } catch (e) { - this._addLogEntry(fromToken, 'offer handling: failed at creating answer', { error: e }); + this._addLogEntry(rtcSession.id, 'offer handling: failed at creating answer', { error: e }); return; } try { await peerConnection.setLocalDescription(answer); } catch (e) { - this._addLogEntry(fromToken, 'offer handling: failed at setting localDescription', { error: e }); + this._addLogEntry(rtcSession.id, 'offer handling: failed at setting localDescription', { error: e }); return; } - this._addLogEntry(fromToken, `sending notification: answer`, { step: 'sending answer' }); - await this._notifyPeers([fromToken], { + this._addLogEntry(rtcSession.id, `sending notification: answer`, { step: 'sending answer' }); + await this._notifyPeers([rtcSession.id], { event: 'answer', payload: { sdp: peerConnection.localDescription }, }); - this._recoverConnection(fromToken, { delay: this.recoveryTimeout, reason: 'standard answer timeout' }); + this._recoverConnection(rtcSession, { delay: this.recoveryTimeout, reason: 'standard answer timeout' }); }, /** * @private @@ -645,7 +645,7 @@ registerModel({ }, /** * @private - * @param {String[]} targetToken + * @param {number[]} targetToken * @param {Object} param1 * @param {String} param1.event * @param {Object} [param1.payload] @@ -684,28 +684,27 @@ registerModel({ } }, /** - * @param {string} token + * @param {RtcSession} rtcSession * @param {string} [reason] */ - async _onRecoverConnectionTimeout(token, reason) { - const rtcSession = this.messaging.models['RtcSession'].insert({ id: token }); + async _onRecoverConnectionTimeout(rtcSession, reason) { rtcSession.update({ connectionRecoveryTimeout: clear() }); - const peerConnection = this._peerConnections[token]; + const peerConnection = this._peerConnections[rtcSession.id]; if (!peerConnection || !this.channel) { return; } - if (this._outGoingCallTokens.has(token)) { + if (this._outGoingCallTokens.has(rtcSession.id)) { return; } if (peerConnection.iceConnectionState === 'connected') { return; } - this._addLogEntry(token, `calling back to recover ${peerConnection.iceConnectionState} connection, reason: ${reason}`); - await this._notifyPeers([token], { + this._addLogEntry(rtcSession.id, `calling back to recover ${peerConnection.iceConnectionState} connection, reason: ${reason}`); + await this._notifyPeers([rtcSession.id], { event: 'disconnect', }); - this._removePeer(token); - this._callPeer(token); + this._removePeer(rtcSession.id); + this._callPeer(rtcSession); }, /** * Pings the server to ensure this session is kept alive. @@ -729,19 +728,18 @@ registerModel({ * from the receiving end. * * @private - * @param {String} token + * @param {RtcSession} rtcSession * @param {Object} [param1] * @param {number} [param1.delay] in ms * @param {string} [param1.reason] */ - _recoverConnection(token, { delay = 0, reason = '' } = {}) { - const rtcSession = this.messaging.models['RtcSession'].insert({ id: token }); + _recoverConnection(rtcSession, { delay = 0, reason = '' } = {}) { if (rtcSession.connectionRecoveryTimeout) { return; } rtcSession.update({ connectionRecoveryTimeout: this.messaging.browser.setTimeout( - this._onRecoverConnectionTimeout.bind(this, token, reason), + this._onRecoverConnectionTimeout.bind(this, rtcSession, reason), delay, ), }); @@ -750,26 +748,26 @@ registerModel({ * Cleans up a peer by closing all its associated content and the connection. * * @private - * @param {String} token + * @param {number} sessionId */ - _removePeer(token) { - const rtcSession = this.messaging.models['RtcSession'].findFromIdentifyingData({ id: token }); + _removePeer(sessionId) { + const rtcSession = this.messaging.models['RtcSession'].findFromIdentifyingData({ id: sessionId }); if (rtcSession) { rtcSession.reset(); } - const dataChannel = this._dataChannels[token]; + const dataChannel = this._dataChannels[sessionId]; if (dataChannel) { dataChannel.close(); } - delete this._dataChannels[token]; - const peerConnection = this._peerConnections[token]; + delete this._dataChannels[sessionId]; + const peerConnection = this._peerConnections[sessionId]; if (peerConnection) { this._removeRemoteTracks(peerConnection); peerConnection.close(); } - delete this._peerConnections[token]; - this._outGoingCallTokens.delete(token); - this._addLogEntry(token, 'peer removed', { step: 'peer removed' }); + delete this._peerConnections[sessionId]; + this._outGoingCallTokens.delete(sessionId); + this._addLogEntry(sessionId, 'peer removed', { step: 'peer removed' }); }, /** * Terminates the Transceivers of the peer connection. @@ -910,7 +908,7 @@ registerModel({ } await this._toggleLocalVideoTrack(trackOptions); for (const [token, peerConnection] of Object.entries(this._peerConnections)) { - await this._updateRemoteTrack(peerConnection, 'video', { token }); + await this._updateRemoteTrack(peerConnection, 'video', { sessionId: Number(token) }); } if (!this.currentRtcSession) { return; @@ -941,21 +939,17 @@ registerModel({ if (!this.videoTrack) { this.currentRtcSession.removeVideo(); } else { - this._updateExternalSessionTrack(this.videoTrack, this.currentRtcSession.peerToken); + this._updateExternalSessionTrack(this.videoTrack, this.currentRtcSession); } }, /** - * Updates the RtcSession associated to the token with a new track. + * Updates the RtcSession with a new track. * * @private * @param {Track} [track] - * @param {String} token the token of video + * @param {RtcSession} rtcSession */ - _updateExternalSessionTrack(track, token) { - const rtcSession = this.messaging.models['RtcSession'].findFromIdentifyingData({ id: token }); - if (!rtcSession) { - return; - } + _updateExternalSessionTrack(track, rtcSession) { const stream = new window.MediaStream(); stream.addTrack(track); @@ -1062,17 +1056,17 @@ registerModel({ * @param {String} trackKind * @param {Object} [param2] * @param {boolean} [param2.initTransceiver] - * @param {String} [param2.token] + * @param {number} [param2.sessionId] */ - async _updateRemoteTrack(peerConnection, trackKind, { initTransceiver, token } = {}) { - this._addLogEntry(token, `updating ${trackKind} transceiver`); + async _updateRemoteTrack(peerConnection, trackKind, { initTransceiver, sessionId } = {}) { + this._addLogEntry(sessionId, `updating ${trackKind} transceiver`); const track = trackKind === 'audio' ? this.audioTrack : this.videoTrack; const fullDirection = track ? 'sendrecv' : 'recvonly'; const limitedDirection = track ? 'sendonly' : 'inactive'; let transceiverDirection = fullDirection; if (trackKind === 'video') { - const focusedToken = this.messaging.focusedRtcSession && this.messaging.focusedRtcSession.peerToken; - transceiverDirection = !focusedToken || focusedToken === token ? fullDirection : limitedDirection; + const focusedSessionId = this.messaging.focusedRtcSession && this.messaging.focusedRtcSession.id; + transceiverDirection = !focusedSessionId || focusedSessionId === sessionId ? fullDirection : limitedDirection; } let transceiver; if (initTransceiver) { @@ -1096,7 +1090,7 @@ registerModel({ // ignored, the transceiver is probably already removed } if (trackKind === 'video') { - this._notifyPeers([token], { + this._notifyPeers([sessionId], { event: 'trackChange', type: 'peerToPeer', payload: { @@ -1118,28 +1112,27 @@ registerModel({ /** * @private * @param {String} state the new state of the connection - * @param {String} token of the peer whose the connection changed + * @param {RtcSession} rtcSession of the peer whose the connection changed */ - async _onConnectionStateChange(state, token) { - this._addLogEntry(token, `connection state changed: ${state}`); + async _onConnectionStateChange(state, rtcSession) { + this._addLogEntry(rtcSession.id, `connection state changed: ${state}`); switch (state) { case "closed": - this._removePeer(token); + this._removePeer(rtcSession.id); break; case "failed": case "disconnected": - await this._recoverConnection(token, { delay: this.recoveryDelay, reason: `connection ${state}` }); + await this._recoverConnection(rtcSession, { delay: this.recoveryDelay, reason: `connection ${state}` }); break; } }, /** * @private * @param {String} connectionState the new state of the connection - * @param {String} token of the peer whose the connection changed + * @param {RtcSession} rtcSession id of the rtcSession of the peer whose the connection changed */ - async _onICEConnectionStateChange(connectionState, token) { - this._addLogEntry(token, `ICE connection state changed: ${connectionState}`, { state: connectionState }); - const rtcSession = this.messaging.models['RtcSession'].findFromIdentifyingData({ id: token }); + async _onICEConnectionStateChange(connectionState, rtcSession) { + this._addLogEntry(rtcSession.id, `ICE connection state changed: ${connectionState}`, { state: connectionState }); if (!rtcSession) { return; } @@ -1148,11 +1141,11 @@ registerModel({ }); switch (connectionState) { case "closed": - this._removePeer(token); + this._removePeer(rtcSession.id); break; case "failed": case "disconnected": - await this._recoverConnection(token, { delay: this.recoveryDelay, reason: `ice connection ${connectionState}` }); + await this._recoverConnection(rtcSession, { delay: this.recoveryDelay, reason: `ice connection ${connectionState}` }); break; } },