From d2f1ca9e022376fc97d265141278d2ca90e0326d Mon Sep 17 00:00:00 2001 From: hippoz Date: Fri, 21 May 2021 01:33:14 +0300 Subject: [PATCH] add support for broken voice backend and add support for serverside experiment toggling --- .../src/API/Gateway/GatewayConnection.js | 105 +++++++++++++++++- .../API/Gateway/globalGatewayConnection.js | 3 +- .../src/Components/Channels/ChannelView.js | 16 ++- bfrontend/src/Components/Main/App.js | 46 ++++---- bfrontend/src/Global/store.js | 11 +- bfrontend/src/Styles/App.scss | 3 +- bfrontend/src/Styles/Components/Card.scss | 3 +- .../src/Styles/Components/Containers.scss | 8 +- 8 files changed, 155 insertions(+), 40 deletions(-) diff --git a/bfrontend/src/API/Gateway/GatewayConnection.js b/bfrontend/src/API/Gateway/GatewayConnection.js index 853bca7..38fcc4b 100644 --- a/bfrontend/src/API/Gateway/GatewayConnection.js +++ b/bfrontend/src/API/Gateway/GatewayConnection.js @@ -1,13 +1,20 @@ import logger from "../../Util/Logger"; const { log: logGateway } = logger([ "Gateway" ]); +const { log: logRtc } = logger([ "Gateway", "RTC" ]); + const opcodes = { 0: { name: "HELLO", data: "JSON" }, 1: { name: "YOO", data: "JSON" }, 2: { name: "YOO_ACK", data: "JSON" }, 3: { name: "ACTION_CREATE_MESSAGE", data: "JSON" }, - 4: { name: "EVENT_CREATE_MESSAGE", data: "JSON" } + 4: { name: "EVENT_CREATE_MESSAGE", data: "JSON" }, + 21: { name: "ACTION_VOICE_REQUEST_SESSION", data: "JSON" }, + 22: { name: "EVENT_VOICE_ASSIGN_SERVER", data: "JSON" }, + 23: { name: "ACTION_VOICE_CONNECTION_REQUEST", data: "JSON" }, + 24: { name: "EVENT_VOICE_CONNECTION_ANSWER", data: "JSON" }, + 25: { name: "EVENT_RENEGOTIATE_REQUIRED", data: "JSON" } }; const opcodeSeparator = "@"; @@ -55,6 +62,7 @@ GatewayConnection.prototype.connect = function(token) { this.handshakeCompleted = false; this.sessionInformation = null; + this.token = token; this.ws.onopen = () => logGateway("Open"); this.ws.onclose = (e) => { @@ -62,7 +70,7 @@ GatewayConnection.prototype.connect = function(token) { logGateway(`Close: ${e.code}:${e.reason}`); this.fire("onclose", e); }; - this.ws.onmessage = (message) => { + this.ws.onmessage = async (message) => { try { const packet = parseMessage(message.data); if (!packet) return console.error("gateway: invalid packet from server"); @@ -86,10 +94,33 @@ GatewayConnection.prototype.connect = function(token) { } case "EVENT_CREATE_MESSAGE": { // New message - // console.log("gateway: got new message", packet.data); this.fire("onmessage", packet.data); break; } + case "EVENT_VOICE_CONNECTION_ANSWER": { + if (!this.webrtcConnection) throw new Error("rtc: got remote answer without local offer"); + if (this.webrtcConnection.signalingState === "stable") { + logRtc("Server sent answer, but we were in stable state"); + return; + } + + const answer = packet.data.answer; + + logRtc("Got remote answer", answer); + + await this.webrtcConnection.setRemoteDescription(answer); + + break; + } + case "EVENT_RENEGOTIATE_REQUIRED": { + if (!this.webrtcConnection) throw new Error("rtc: got remote EVENT_RENEGOTIATE_REQUIRED without local offer"); + + logRtc("Server requested renegotiation"); + + this.negotiateVoiceSession(); + + break; + } default: { logGateway("Got unknown packet", message.data); break; @@ -112,6 +143,74 @@ GatewayConnection.prototype.sendMessage = function(content, channelId) { })); }; +GatewayConnection.prototype.negotiateVoiceSession = async function() { + if (this.webrtcConnection.connectionState === "connected" || this.webrtcConnection.connectionState === "connecting") return; + if (this.webrtcConnection.signalingState !== "stable") return; + + logRtc("Negotiating voice session..."); + this.webrtcConnection.ontrack = (e) => { + logRtc("Got remote track", e); + const audio = document.createElement("audio"); + audio.srcObject = e.streams[0]; + audio.controls = true; + audio.play(); + document.body.appendChild(audio); + }; + + this.webrtcConnection.onnegotiationneeded = () => this.negotiateVoiceSession(); + + this.webrtcConnection.onicecandidate = (event) => { + if (event.candidate || this.rtcGotCandidates) return; + this.rtcGotCandidates = true; + logRtc("End of candidates; we can send the offer to the server", this.webrtcConnection.localDescription); + this.ws.send(this.packet("ACTION_VOICE_CONNECTION_REQUEST", { + token: this.token, + channel: { + _id: this.currentVoiceChannel + }, + offer: this.webrtcConnection.localDescription + })); + }; + + this.webrtcConnection.onsignalingstatechange = () => { + logRtc(`onsignalingstatechange -> ${this.webrtcConnection.signalingState}`); + }; + + this.webrtcConnection.onconnectionstatechange = () => { + logRtc(`onsignalingstatechange -> ${this.webrtcConnection.connectionState}`); + }; + + + const offer = await this.webrtcConnection.createOffer(); + await this.webrtcConnection.setLocalDescription(offer); + if (this.rtcGotCandidates) { + logRtc("Already have candidates; we can send the offer to the server", this.webrtcConnection.localDescription); + this.ws.send(this.packet("ACTION_VOICE_CONNECTION_REQUEST", { + token: this.token, + channel: { + _id: this.currentVoiceChannel + }, + offer: this.webrtcConnection.localDescription + })); + } +}; + +GatewayConnection.prototype.beginVoiceSession = async function(channelId) { + logRtc("beginVoiceSession"); + this.currentVoiceChannel = channelId; + this.webrtcConnection = new RTCPeerConnection({ + iceServers: [ + { + urls: "stun://stun.l.google.com:19302" + } + ] + }); + + this.webrtcConnection.addTrack((await navigator.mediaDevices.getUserMedia({ + audio: true + })).getTracks()[0]); + await this.negotiateVoiceSession(); +}; GatewayConnection.prototype.packet = function(op, data) { if (typeof op === "string") op = getOpcodeByName(op); diff --git a/bfrontend/src/API/Gateway/globalGatewayConnection.js b/bfrontend/src/API/Gateway/globalGatewayConnection.js index 48fe236..8dcf674 100644 --- a/bfrontend/src/API/Gateway/globalGatewayConnection.js +++ b/bfrontend/src/API/Gateway/globalGatewayConnection.js @@ -7,7 +7,8 @@ const globalGatewayConnection = new GatewayConnection(config.gatewayUrl); globalGatewayConnection.onopen = (sessionData) => { store.dispatch({ type: 'gateway/connectionstatus', gateway: { isConnected: true } }); store.dispatch({ type: 'authenticator/updatelocaluserobject', user: sessionData.user }); - store.dispatch({ type: 'channels/updatechannellist', channels: sessionData.channels }) + store.dispatch({ type: 'channels/updatechannellist', channels: sessionData.channels }); + store.dispatch({ type: 'application/updateexperiments', user: sessionData.__global_experiments || [] }); }; globalGatewayConnection.onmessage = (message) => { diff --git a/bfrontend/src/Components/Channels/ChannelView.js b/bfrontend/src/Components/Channels/ChannelView.js index cb54307..0761761 100644 --- a/bfrontend/src/Components/Channels/ChannelView.js +++ b/bfrontend/src/Components/Channels/ChannelView.js @@ -9,7 +9,7 @@ import { connect, useDispatch } from 'react-redux'; import { useState, useRef, useEffect } from 'react'; -const ChannelView = ({ channels, messages, channel, selectedChannelId }) => { +const ChannelView = ({ channels, messages, channel, selectedChannelId, experiments }) => { const { id } = useParams(); const [ textInput, setTextInput ] = useState(''); @@ -52,13 +52,16 @@ const ChannelView = ({ channels, messages, channel, selectedChannelId }) => { } return ( -
+
+ { (experiments.voiceSFUTesting) && }
-
-
+
+
{ messagesView }
@@ -68,7 +71,7 @@ const ChannelView = ({ channels, messages, channel, selectedChannelId }) => { setTextInput(target.value) }>
- + { (experiments.userListTest) && }
) @@ -90,13 +93,14 @@ const ChannelView = ({ channels, messages, channel, selectedChannelId }) => { }; const stateToProps = (state, ownProps) => { - const channelId = ownProps.match.params.id; // NOTE(hippoz): kind of a hack, but it works and idk if theres any other solution + const channelId = ownProps.match.params.id; // NOTE: kind of a hack, but it works and idk if theres any other solution return { channels: state?.channels, channel: state?.channels?.find(x => x._id === channelId), messages: state?.messages[channelId] || [], selectedChannelId: state?.selectedChannelId, + experiments: state?.experiments || {} }; }; diff --git a/bfrontend/src/Components/Main/App.js b/bfrontend/src/Components/Main/App.js index 3799f27..5c8860d 100644 --- a/bfrontend/src/Components/Main/App.js +++ b/bfrontend/src/Components/Main/App.js @@ -30,34 +30,32 @@ function App({ user }) { if (!hasError) { return ( -
-
- - - - { - return ( - <> - - - - ); - }} - /> - - - { user && } - - - - -
+
+ + + + { + return ( + <> + + + + ); + }} + /> + + + { user && } + + + +
); } else { return ( -
+
); diff --git a/bfrontend/src/Global/store.js b/bfrontend/src/Global/store.js index 63b3221..83dcfda 100644 --- a/bfrontend/src/Global/store.js +++ b/bfrontend/src/Global/store.js @@ -42,8 +42,8 @@ const reducer = (state = intitialState, payload) => { ...state, messages: { ...state.messages, - [payload.message.channel_id]: [ - ...state.messages[payload.message.channel_id] || [], + [payload.message.channel._id]: [ + ...state.messages[payload.message.channel._id] || [], payload.message ] } @@ -67,6 +67,13 @@ const reducer = (state = intitialState, payload) => { }; } + case 'application/updateexperiments': { + return { + ...state, + experiments: payload.experiments + } + } + default: { return state; } diff --git a/bfrontend/src/Styles/App.scss b/bfrontend/src/Styles/App.scss index 36af9ec..f3cdc84 100644 --- a/bfrontend/src/Styles/App.scss +++ b/bfrontend/src/Styles/App.scss @@ -23,7 +23,8 @@ body { color: var(--default-text-color); background-color: var(--background-color); font-family: Noto Sans,-apple-system,BlinkMacSystemFont,sans-serif; - + min-height: 100vh; + max-height: 100vh; margin: 0px; padding: 0px; } \ No newline at end of file diff --git a/bfrontend/src/Styles/Components/Card.scss b/bfrontend/src/Styles/Components/Card.scss index d508c59..c32ae03 100644 --- a/bfrontend/src/Styles/Components/Card.scss +++ b/bfrontend/src/Styles/Components/Card.scss @@ -41,9 +41,8 @@ background-color: var(--channel-message-list-background-color); box-shadow: none; - + flex-basis: 100%; border-radius: 0px; - flex-grow: 1; overflow: auto; } diff --git a/bfrontend/src/Styles/Components/Containers.scss b/bfrontend/src/Styles/Components/Containers.scss index 342b36d..d774b54 100644 --- a/bfrontend/src/Styles/Components/Containers.scss +++ b/bfrontend/src/Styles/Components/Containers.scss @@ -37,6 +37,12 @@ z-index: 100; } -.channel-view { +.channel-message-panel { + display: flex; + flex-direction: column; background-color: var(--channel-view-container-color); +} + +.hidden-overflow { + overflow: hidden; } \ No newline at end of file