From d642928ee7c7b2599138c1f9f419c5b77fb2f5cc Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Sat, 5 Dec 2020 22:58:21 +0100 Subject: [PATCH 1/8] Support TURN server and custom STUN server --- src/renderer/App.tsx | 6 +- src/renderer/Settings.tsx | 111 +++++++++++++++++++++++++++++++--- src/renderer/Voice.tsx | 27 +++++++-- src/renderer/css/settings.css | 21 ++++++- 4 files changed, 148 insertions(+), 17 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index c572eee9..a0d2c1e4 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -39,7 +39,11 @@ function App() { data: '' }, hideCode: false, - stereoInLobby: true + stereoInLobby: true, + stunServerURL: 'stun:stun.l.google.com:19302', + turnServerURL: null, + turnUsername: null, + turnPassword: null }); useEffect(() => { diff --git a/src/renderer/Settings.tsx b/src/renderer/Settings.tsx index 379ef587..a02872dc 100644 --- a/src/renderer/Settings.tsx +++ b/src/renderer/Settings.tsx @@ -1,5 +1,5 @@ import Store from 'electron-store'; -import React, { useContext, useEffect, useReducer, useState } from "react"; +import React, { SVGProps, useContext, useEffect, useReducer, useState } from "react"; import { SettingsContext } from "./App"; import Ajv from 'ajv'; import './css/settings.css'; @@ -83,6 +83,24 @@ const store = new Store({ stereoInLobby: { type: 'boolean', default: true + }, + stunServerURL: { + type: ['string', 'null'], + format: 'uri', + default: 'stun:stun.l.google.com:19302' + }, + turnServerURL: { + type: ['string', 'null'], + format: 'uri', + default: null + }, + turnUsername: { + type: ['string', 'null'], + default: null + }, + turnPassword: { + type: ['string', 'null'], + default: null } } }); @@ -106,6 +124,10 @@ export interface ISettings { }, hideCode: boolean; stereoInLobby: boolean; + stunServerURL: string | null; + turnServerURL: string | null; + turnUsername: string | null; + turnPassword: string | null; } export const settingsReducer = (state: ISettings, action: { type: 'set' | 'setOne', action: [string, any] | ISettings @@ -152,6 +174,32 @@ function URLInput({ initialURL, onValidURL }: URLInputProps) { return } +function Arrow(props: SVGProps) { + return + + + +} + +type CollapsibleSettingsProps = { + entryName: string, + collapsibleContent: JSX.Element +}; + +function CollapsibleSettings({ collapsibleContent, entryName }: CollapsibleSettingsProps) { + const [collapsed, setCollapsed] = useState(true); + + return
+ +
+ {collapsed ? '' : collapsibleContent} +
+
; +} + export default function Settings({ open, onClose }: SettingsProps) { const [settings, setSettings] = useContext(SettingsContext); const [unsavedCount, setUnsavedCount] = useState(0); @@ -165,7 +213,15 @@ export default function Settings({ open, onClose }: SettingsProps) { useEffect(() => { setUnsavedCount(s => s + 1); - }, [settings.microphone, settings.speaker, settings.serverURL]); + }, [ + settings.microphone, + settings.speaker, + settings.serverURL, + settings.stunServerURL, + settings.turnServerURL, + settings.turnUsername, + settings.turnPassword + ]); const [devices, setDevices] = useState([]); const [_, updateDevices] = useReducer((state) => state + 1, 0); @@ -213,17 +269,14 @@ export default function Settings({ open, onClose }: SettingsProps) { const speakers = devices.filter(d => d.kind === 'audiooutput'); return
- { + { if (unsaved) { onClose(); location.reload(); - } - else + } else { onClose(); - }}> - - - + } + }} /> {/*
{ ipcRenderer.send('alwaysOnTop', !settings.alwaysOnTop); setSettings({ @@ -314,6 +367,46 @@ export default function Settings({ open, onClose }: SettingsProps) {
+ +
+ + { + setSettings({ + type: 'setOne', + action: ['stunServerURL', url !== '' ? url : null] + }) + }} /> +
+
+ + { + setSettings({ + type: 'setOne', + action: ['turnServerURL', url !== '' ? url : null] + }) + }} /> +
+
+ + { + setSettings({ + type: 'setOne', + action: ['turnUsername', e.target.value !== '' ? e.target.value : null] + }) + }} /> +
+
+ + { + setSettings({ + type: 'setOne', + action: ['turnPassword', e.target.value !== '' ? e.target.value : null] + }) + }} /> +
+
+ } /> } \ No newline at end of file diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 8c2a874e..2f1f966c 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -234,15 +234,30 @@ export default function Voice() { setConnect({ connect }); function createPeerConnection(peer: string, initiator: boolean) { // console.log("Opening connection to ", peer, "Initiator: ", initiator); + const iceServers = []; + + if (settings.stunServerURL) { + iceServers.push({ + urls: settings.stunServerURL + }) + } + + if (settings.turnServerURL && settings.turnUsername && settings.turnPassword) { + iceServers.push({ + urls: settings.turnServerURL, + username: settings.turnUsername, + credential: settings.turnPassword + }) + } + const connection = new Peer({ - stream, initiator, config: { - iceServers: [ - { - 'urls': 'stun:stun.l.google.com:19302' - } - ] + stream, + initiator, + config: { + iceServers } }); + peerConnections[peer] = connection; connection.on('stream', (stream: MediaStream) => { diff --git a/src/renderer/css/settings.css b/src/renderer/css/settings.css index 6eb04eca..0e01315a 100644 --- a/src/renderer/css/settings.css +++ b/src/renderer/css/settings.css @@ -38,7 +38,7 @@ transform: translateX(-1px); } -input[type="text"] { +input[type="text"], input[type="password"] { background: #1d1d1d; border: 1px solid rgba(255, 255, 255, 0.5); outline: none; @@ -91,4 +91,23 @@ select { .test-speakers { width: fit-content; margin: 5px auto; +} + +.inline-icon { + display: inline; + margin-right: 8px; + vertical-align: middle; +} + +.clickable, .clickable label { + cursor: pointer; +} + +.collapsible-content-wrapper { + width: 100%; + text-align: center; +} + +.collapsible-content { + padding-top: 12px; } \ No newline at end of file From dd23ce66192b4c27b2fe510542ae7ea610933e22 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Sat, 5 Dec 2020 23:34:43 +0100 Subject: [PATCH 2/8] Small linting fix --- src/renderer/Voice.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 2f1f966c..4a1456d1 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -239,7 +239,7 @@ export default function Voice() { if (settings.stunServerURL) { iceServers.push({ urls: settings.stunServerURL - }) + }); } if (settings.turnServerURL && settings.turnUsername && settings.turnPassword) { @@ -247,7 +247,7 @@ export default function Voice() { urls: settings.turnServerURL, username: settings.turnUsername, credential: settings.turnPassword - }) + }); } const connection = new Peer({ From 846628da2f9ecfe0452280a0a31f39cf8d6d2dad Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Sun, 6 Dec 2020 19:40:15 +0100 Subject: [PATCH 3/8] Allow custom ICE config from server --- src/renderer/App.tsx | 6 +- src/renderer/Settings.tsx | 111 +++------------------------------- src/renderer/Voice.tsx | 31 ++++------ src/renderer/css/settings.css | 2 +- 4 files changed, 23 insertions(+), 127 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index a0d2c1e4..c572eee9 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -39,11 +39,7 @@ function App() { data: '' }, hideCode: false, - stereoInLobby: true, - stunServerURL: 'stun:stun.l.google.com:19302', - turnServerURL: null, - turnUsername: null, - turnPassword: null + stereoInLobby: true }); useEffect(() => { diff --git a/src/renderer/Settings.tsx b/src/renderer/Settings.tsx index a02872dc..379ef587 100644 --- a/src/renderer/Settings.tsx +++ b/src/renderer/Settings.tsx @@ -1,5 +1,5 @@ import Store from 'electron-store'; -import React, { SVGProps, useContext, useEffect, useReducer, useState } from "react"; +import React, { useContext, useEffect, useReducer, useState } from "react"; import { SettingsContext } from "./App"; import Ajv from 'ajv'; import './css/settings.css'; @@ -83,24 +83,6 @@ const store = new Store({ stereoInLobby: { type: 'boolean', default: true - }, - stunServerURL: { - type: ['string', 'null'], - format: 'uri', - default: 'stun:stun.l.google.com:19302' - }, - turnServerURL: { - type: ['string', 'null'], - format: 'uri', - default: null - }, - turnUsername: { - type: ['string', 'null'], - default: null - }, - turnPassword: { - type: ['string', 'null'], - default: null } } }); @@ -124,10 +106,6 @@ export interface ISettings { }, hideCode: boolean; stereoInLobby: boolean; - stunServerURL: string | null; - turnServerURL: string | null; - turnUsername: string | null; - turnPassword: string | null; } export const settingsReducer = (state: ISettings, action: { type: 'set' | 'setOne', action: [string, any] | ISettings @@ -174,32 +152,6 @@ function URLInput({ initialURL, onValidURL }: URLInputProps) { return } -function Arrow(props: SVGProps) { - return - - - -} - -type CollapsibleSettingsProps = { - entryName: string, - collapsibleContent: JSX.Element -}; - -function CollapsibleSettings({ collapsibleContent, entryName }: CollapsibleSettingsProps) { - const [collapsed, setCollapsed] = useState(true); - - return
- -
- {collapsed ? '' : collapsibleContent} -
-
; -} - export default function Settings({ open, onClose }: SettingsProps) { const [settings, setSettings] = useContext(SettingsContext); const [unsavedCount, setUnsavedCount] = useState(0); @@ -213,15 +165,7 @@ export default function Settings({ open, onClose }: SettingsProps) { useEffect(() => { setUnsavedCount(s => s + 1); - }, [ - settings.microphone, - settings.speaker, - settings.serverURL, - settings.stunServerURL, - settings.turnServerURL, - settings.turnUsername, - settings.turnPassword - ]); + }, [settings.microphone, settings.speaker, settings.serverURL]); const [devices, setDevices] = useState([]); const [_, updateDevices] = useReducer((state) => state + 1, 0); @@ -269,14 +213,17 @@ export default function Settings({ open, onClose }: SettingsProps) { const speakers = devices.filter(d => d.kind === 'audiooutput'); return
- { + { if (unsaved) { onClose(); location.reload(); - } else { - onClose(); } - }} /> + else + onClose(); + }}> + + + {/*
{ ipcRenderer.send('alwaysOnTop', !settings.alwaysOnTop); setSettings({ @@ -367,46 +314,6 @@ export default function Settings({ open, onClose }: SettingsProps) {
- -
- - { - setSettings({ - type: 'setOne', - action: ['stunServerURL', url !== '' ? url : null] - }) - }} /> -
-
- - { - setSettings({ - type: 'setOne', - action: ['turnServerURL', url !== '' ? url : null] - }) - }} /> -
-
- - { - setSettings({ - type: 'setOne', - action: ['turnUsername', e.target.value !== '' ? e.target.value : null] - }) - }} /> -
-
- - { - setSettings({ - type: 'setOne', - action: ['turnPassword', e.target.value !== '' ? e.target.value : null] - }) - }} /> -
-
- } /> } \ No newline at end of file diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 4a1456d1..3f0e506d 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -44,6 +44,14 @@ interface OtherDead { [playerId: number]: boolean; // isTalking } +const DEFAULT_ICE_CONFIG: RTCConfiguration = { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + } + ] +} + // function clamp(number: number, min: number, max: number): number { // if (min > max) { // let tmp = max; @@ -154,6 +162,7 @@ export default function Voice() { socket.on('connect', () => { setConnected(true); }); + socket.on('disconnect', () => { setConnected(false); }); @@ -162,6 +171,8 @@ export default function Voice() { let audioListener: any; let audio: boolean | MediaTrackConstraints = true; + let iceConfig : RTCConfiguration = DEFAULT_ICE_CONFIG; + socket.on('iceConfig', (newIceConfig: RTCConfiguration) => iceConfig = newIceConfig) // Get microphone settings if (settings.microphone.toLowerCase() !== 'default') @@ -234,28 +245,10 @@ export default function Voice() { setConnect({ connect }); function createPeerConnection(peer: string, initiator: boolean) { // console.log("Opening connection to ", peer, "Initiator: ", initiator); - const iceServers = []; - - if (settings.stunServerURL) { - iceServers.push({ - urls: settings.stunServerURL - }); - } - - if (settings.turnServerURL && settings.turnUsername && settings.turnPassword) { - iceServers.push({ - urls: settings.turnServerURL, - username: settings.turnUsername, - credential: settings.turnPassword - }); - } - const connection = new Peer({ stream, initiator, - config: { - iceServers - } + config: iceConfig }); peerConnections[peer] = connection; diff --git a/src/renderer/css/settings.css b/src/renderer/css/settings.css index 0e01315a..d08b0ca2 100644 --- a/src/renderer/css/settings.css +++ b/src/renderer/css/settings.css @@ -38,7 +38,7 @@ transform: translateX(-1px); } -input[type="text"], input[type="password"] { +input[type="text"] { background: #1d1d1d; border: 1px solid rgba(255, 255, 255, 0.5); outline: none; From 42941d810d911c867f6d1f189e67d0454a1101b9 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Mon, 7 Dec 2020 01:31:35 +0100 Subject: [PATCH 4/8] Use a custom format for ICEServers and validate it --- src/renderer/Voice.tsx | 43 +++++++++++++++++++++++++++--- src/renderer/validatePeerConfig.ts | 33 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/renderer/validatePeerConfig.ts diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 3f0e506d..11e4f836 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -7,6 +7,7 @@ import Peer from 'simple-peer'; import { ipcRenderer, remote } from 'electron'; import VAD from './vad'; import { ISettings } from './Settings'; +import { validatePeerConfig } from './validatePeerConfig'; interface PeerConnections { [peer: string]: Peer.Instance; @@ -44,6 +45,18 @@ interface OtherDead { [playerId: number]: boolean; // isTalking } +interface ICEServer { + url: string, + username: string | undefined, + credential: string | undefined, +} + +interface PeerConfig { + forceRelayOnly: Boolean, + stunServers: ICEServer[], + turnServers: ICEServer[] +} + const DEFAULT_ICE_CONFIG: RTCConfiguration = { iceServers: [ { @@ -167,13 +180,37 @@ export default function Voice() { setConnected(false); }); + let iceConfig: RTCConfiguration = DEFAULT_ICE_CONFIG; + socket.on('peerConfig', (peerConfig: PeerConfig) => { + if (validatePeerConfig(peerConfig)) { + if (peerConfig.forceRelayOnly && !peerConfig.turnServers) { + alert(`Server has forced relay mode enabled but provides no relay servers. Default config will be used.`); + return; + } + + iceConfig = { + iceTransportPolicy: peerConfig.forceRelayOnly ? 'relay' : 'all', + iceServers: [...(peerConfig.stunServers || []), ...(peerConfig.turnServers || [])] + .map((server) => { + return { + urls: server.url, + username: server.username, + credential: server.credential + } + }) + }; + } else { + alert(`Server sent a malformed peer config. Default config will be used.${ + validatePeerConfig.errors ? + ` See errors below:\n${validatePeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : `` + }`); + } + }) + // Initialize variables let audioListener: any; let audio: boolean | MediaTrackConstraints = true; - let iceConfig : RTCConfiguration = DEFAULT_ICE_CONFIG; - socket.on('iceConfig', (newIceConfig: RTCConfiguration) => iceConfig = newIceConfig) - // Get microphone settings if (settings.microphone.toLowerCase() !== 'default') audio = { deviceId: settings.microphone }; diff --git a/src/renderer/validatePeerConfig.ts b/src/renderer/validatePeerConfig.ts new file mode 100644 index 00000000..c7d76726 --- /dev/null +++ b/src/renderer/validatePeerConfig.ts @@ -0,0 +1,33 @@ +import Ajv from 'ajv'; + +const ICE_SERVER_DEFINITION = { + type: 'array', + items: { + type: 'object', + properties: { + url: { + type: 'string', + format: 'uri' + }, + username: { + type: 'string', + }, + credential: { + type: 'string', + } + }, + required: ['url'] + } +} + +export const validatePeerConfig = new Ajv({ format: 'full', allErrors: true }).compile({ + type: 'object', + properties: { + forceRelayOnly: { + type: 'boolean' + }, + stunServers: ICE_SERVER_DEFINITION, + turnServers: ICE_SERVER_DEFINITION, + }, + required: ['forceRelayOnly'] +}); From a0a38a14d28f22f2d14103fc37b302869a41cc93 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Mon, 7 Dec 2020 16:20:51 +0100 Subject: [PATCH 5/8] Use return early pattern for peerconfig validation --- src/renderer/Voice.tsx | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 11e4f836..17465584 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -182,29 +182,29 @@ export default function Voice() { let iceConfig: RTCConfiguration = DEFAULT_ICE_CONFIG; socket.on('peerConfig', (peerConfig: PeerConfig) => { - if (validatePeerConfig(peerConfig)) { - if (peerConfig.forceRelayOnly && !peerConfig.turnServers) { - alert(`Server has forced relay mode enabled but provides no relay servers. Default config will be used.`); - return; - } + if (!validatePeerConfig(peerConfig)) { + alert(`Server sent a malformed peer config. Default config will be used.${validatePeerConfig.errors ? + ` See errors below:\n${validatePeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : `` + }`); + return; + } - iceConfig = { - iceTransportPolicy: peerConfig.forceRelayOnly ? 'relay' : 'all', - iceServers: [...(peerConfig.stunServers || []), ...(peerConfig.turnServers || [])] - .map((server) => { - return { - urls: server.url, - username: server.username, - credential: server.credential - } - }) - }; - } else { - alert(`Server sent a malformed peer config. Default config will be used.${ - validatePeerConfig.errors ? - ` See errors below:\n${validatePeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : `` - }`); + if (peerConfig.forceRelayOnly && !peerConfig.turnServers) { + alert(`Server has forced relay mode enabled but provides no relay servers. Default config will be used.`); + return; } + + iceConfig = { + iceTransportPolicy: peerConfig.forceRelayOnly ? 'relay' : 'all', + iceServers: [...(peerConfig.stunServers || []), ...(peerConfig.turnServers || [])] + .map((server) => { + return { + urls: server.url, + username: server.username, + credential: server.credential + } + }) + }; }) // Initialize variables From 631756642464b8150ab20cc8382a2b5832412123 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Mon, 7 Dec 2020 16:23:19 +0100 Subject: [PATCH 6/8] Remove obsolete CSS --- src/renderer/css/settings.css | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/renderer/css/settings.css b/src/renderer/css/settings.css index d08b0ca2..2266c3cc 100644 --- a/src/renderer/css/settings.css +++ b/src/renderer/css/settings.css @@ -92,22 +92,3 @@ select { width: fit-content; margin: 5px auto; } - -.inline-icon { - display: inline; - margin-right: 8px; - vertical-align: middle; -} - -.clickable, .clickable label { - cursor: pointer; -} - -.collapsible-content-wrapper { - width: 100%; - text-align: center; -} - -.collapsible-content { - padding-top: 12px; -} \ No newline at end of file From 264f9013b8203864b59ca7d6ee3a9215b7c49e64 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Sun, 27 Dec 2020 20:27:21 +0100 Subject: [PATCH 7/8] Implement changes to peer config --- .vscode/settings.json | 5 +++ src/renderer/Voice.tsx | 48 ++++++++++-------------- src/renderer/validateClientPeerConfig.ts | 34 +++++++++++++++++ src/renderer/validatePeerConfig.ts | 33 ---------------- 4 files changed, 58 insertions(+), 62 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/renderer/validateClientPeerConfig.ts delete mode 100644 src/renderer/validatePeerConfig.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ef17c7d5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "search.exclude": { + "**/node_modules": false + } +} \ No newline at end of file diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index c29898c8..44be0dd3 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -7,7 +7,7 @@ import Peer from 'simple-peer'; import { ipcRenderer, remote } from 'electron'; import VAD from './vad'; import { ISettings } from '../common/ISettings'; -import { validatePeerConfig } from './validatePeerConfig'; +import { validateClientPeerConfig } from './validateClientPeerConfig'; export interface ExtendedAudioElement extends HTMLAudioElement { setSinkId: (sinkId: string) => Promise; @@ -44,16 +44,9 @@ interface OtherDead { [playerId: number]: boolean; // isTalking } -interface ICEServer { - url: string, - username: string | undefined, - credential: string | undefined, -} - -interface PeerConfig { - forceRelayOnly: Boolean, - stunServers: ICEServer[], - turnServers: ICEServer[] +interface ClientPeerConfig { + forceRelayOnly: boolean, + iceServers: RTCIceServer[] } const DEFAULT_ICE_CONFIG: RTCConfiguration = { @@ -62,7 +55,7 @@ const DEFAULT_ICE_CONFIG: RTCConfiguration = { urls: 'stun:stun.l.google.com:19302' } ] -} +}; function calculateVoiceAudio(state: AmongUsState, settings: ISettings, me: Player, other: Player, gain: GainNode, pan: PannerNode): void { const audioContext = pan.context; @@ -172,31 +165,27 @@ const Voice: React.FC = function () { }); let iceConfig: RTCConfiguration = DEFAULT_ICE_CONFIG; - socket.on('peerConfig', (peerConfig: PeerConfig) => { - if (!validatePeerConfig(peerConfig)) { - alert(`Server sent a malformed peer config. Default config will be used.${validatePeerConfig.errors ? - ` See errors below:\n${validatePeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : `` - }`); + socket.on('clientPeerConfig', (clientPeerConfig: ClientPeerConfig) => { + if (!validateClientPeerConfig(clientPeerConfig)) { + alert(`Server sent a malformed peer config. Default config will be used.${validateClientPeerConfig.errors ? + ` See errors below:\n${validateClientPeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : '' + }`); return; } - if (peerConfig.forceRelayOnly && !peerConfig.turnServers) { - alert(`Server has forced relay mode enabled but provides no relay servers. Default config will be used.`); + if ( + clientPeerConfig.forceRelayOnly && + !clientPeerConfig.iceServers.some(server => server.urls.toString().includes('turn:')) + ) { + alert('Server has forced relay mode enabled but provides no relay servers. Default config will be used.'); return; } iceConfig = { - iceTransportPolicy: peerConfig.forceRelayOnly ? 'relay' : 'all', - iceServers: [...(peerConfig.stunServers || []), ...(peerConfig.turnServers || [])] - .map((server) => { - return { - urls: server.url, - username: server.username, - credential: server.credential - } - }) + iceTransportPolicy: clientPeerConfig.forceRelayOnly ? 'relay' : 'all', + iceServers: clientPeerConfig.iceServers }; - }) + }); // Initialize variables let audioListener: { @@ -282,6 +271,7 @@ const Voice: React.FC = function () { }; setConnect({ connect }); function createPeerConnection(peer: string, initiator: boolean) { + console.log(iceConfig); const connection = new Peer({ stream, initiator, diff --git a/src/renderer/validateClientPeerConfig.ts b/src/renderer/validateClientPeerConfig.ts new file mode 100644 index 00000000..867d76c6 --- /dev/null +++ b/src/renderer/validateClientPeerConfig.ts @@ -0,0 +1,34 @@ +import Ajv from 'ajv'; + +export const validateClientPeerConfig = new Ajv({ format: 'full', allErrors: true }).compile({ + type: 'object', + properties: { + forceRelayOnly: { + type: 'boolean' + }, + iceServers: { + type: 'array', + items: { + type: 'object', + properties: { + urls: { + type: ['string', 'array'], + format: 'uri', + items: { + type: 'string', + format: 'uri' + } + }, + username: { + type: 'string', + }, + credential: { + type: 'string', + } + }, + required: ['urls'] + } + } + }, + required: ['forceRelayOnly', 'iceServers'] +}); diff --git a/src/renderer/validatePeerConfig.ts b/src/renderer/validatePeerConfig.ts deleted file mode 100644 index c7d76726..00000000 --- a/src/renderer/validatePeerConfig.ts +++ /dev/null @@ -1,33 +0,0 @@ -import Ajv from 'ajv'; - -const ICE_SERVER_DEFINITION = { - type: 'array', - items: { - type: 'object', - properties: { - url: { - type: 'string', - format: 'uri' - }, - username: { - type: 'string', - }, - credential: { - type: 'string', - } - }, - required: ['url'] - } -} - -export const validatePeerConfig = new Ajv({ format: 'full', allErrors: true }).compile({ - type: 'object', - properties: { - forceRelayOnly: { - type: 'boolean' - }, - stunServers: ICE_SERVER_DEFINITION, - turnServers: ICE_SERVER_DEFINITION, - }, - required: ['forceRelayOnly'] -}); From 1c5e0bae4bd9f59771e890852bd531e9191181bc Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Sun, 27 Dec 2020 22:03:40 +0100 Subject: [PATCH 8/8] Remove obsolete CSS file from fork --- src/renderer/css/settings.css | 110 ---------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 src/renderer/css/settings.css diff --git a/src/renderer/css/settings.css b/src/renderer/css/settings.css deleted file mode 100644 index 0349ab92..00000000 --- a/src/renderer/css/settings.css +++ /dev/null @@ -1,110 +0,0 @@ -#settings { - transition: transform .2s ease-in; - width: 100vw; - height: 100vh; - background: #171717ad; - backdrop-filter: blur(4px); - position: absolute; - top: 0; - left: 0; - z-index: 10; - display: flex; - flex-direction: column; - justify-content: start; - align-items: center; - padding-top: 20px; -} - -.form-control { - user-select: none; -} - -.form-control.l { - display: flex; - flex-direction: column; - text-align: center; -} - -.form-control.m { - margin-bottom: 10px; -} - -.titlebar-button.back { - right: 2px; - transition: transform .1s linear; -} - -.titlebar-button.back:hover { - transform: translateX(-1px); -} - -input[type="text"] { - background: #1d1d1d; - border: 1px solid rgba(255, 255, 255, 0.5); - outline: none; - color: white; - padding: 5px; - border-radius: 5px; -} - -.form-control.m .input-error { - border-color: #b00020 -} - -input[type="text"]:focus { - border-color: white; -} - -select { - background: #1d1d1d; - border: 1px solid rgba(255, 255, 255, 0.5); - outline: none; - color: white; - padding: 5px; - border-radius: 5px; - max-width: 240px; -} - -.settings-scroll { - overflow-y: auto; - height: calc(100vh - 50px); - display: flex; - flex-direction: column; - justify-content: start; - align-items: center; - margin-bottom: 30px; - width: 100%; -} - -.microphone-bar { - width: 200px; - height: 10px; - background: #1d1d1d; - border: 1px solid rgba(255, 255, 255, 0.5); - border-radius: 5px; - margin: 5px auto; - overflow: hidden; -} - -.microphone-bar-inner { - background: #e74c3c; - height: 10px; - border-radius: 5px; -} - -.test-speakers { - width: fit-content; - margin: 5px auto; -} - -.settings-alert { - background: #f1c40f; - color: black; - position: absolute; - bottom: 20px; - left: 0; - height: 30px; - justify-content: center; - align-items: center; - width: 100vw; -}