diff --git a/desktop-app/app/actions/networkConfig.js b/desktop-app/app/actions/networkConfig.js
new file mode 100644
index 00000000..7f0aa4a5
--- /dev/null
+++ b/desktop-app/app/actions/networkConfig.js
@@ -0,0 +1,52 @@
+import pubsub from 'pubsub.js';
+import type {Dispatch, GetState} from '../reducers/types';
+import {
+ SET_NETWORK_TROTTLING_PROFILE,
+ CLEAR_NETWORK_CACHE
+} from '../constants/pubsubEvents';
+
+export const CHANGE_ACTIVE_THROTTLING_PROFILE = 'CHANGE_ACTIVE_THROTTLING_PROFILE';
+export const SAVE_THROTTLING_PROFILES = 'SAVE_THROTTLING_PROFILES';
+
+export function changeActiveThrottlingProfile(title='Online') {
+ return {
+ type: CHANGE_ACTIVE_THROTTLING_PROFILE,
+ title,
+ };
+}
+
+export function saveThrottlingProfilesList(profiles) {
+ return {
+ type: SAVE_THROTTLING_PROFILES,
+ profiles,
+ };
+}
+
+export function onActiveThrottlingProfileChanged(title) {
+ return (dispatch: Dispatch, getState: GetState) => {
+ const {
+ browser: {networkConfiguration: {throttling}},
+ } = getState();
+
+ const activeProfile = throttling.find(x => x.title === title);
+
+ if (activeProfile != null) {
+ pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);
+ dispatch(changeActiveThrottlingProfile(title))
+ }
+ };
+}
+
+export function onThrottlingProfilesListChanged(profiles) {
+ return (dispatch: Dispatch, getState: GetState) => {
+ dispatch(saveThrottlingProfilesList(profiles));
+ const activeProfile = profiles.find(x => x.type === 'Online');
+ pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);
+ };
+}
+
+export function onClearNetworkCache() {
+ return (dispatch: Dispatch, getState: GetState) => {
+ pubsub.publish(CLEAR_NETWORK_CACHE);
+ };
+}
diff --git a/desktop-app/app/components/ClearNetworkCache/index.js b/desktop-app/app/components/ClearNetworkCache/index.js
new file mode 100644
index 00000000..8cb4ca97
--- /dev/null
+++ b/desktop-app/app/components/ClearNetworkCache/index.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import cx from 'classnames';
+import Button from '@material-ui/core/Button';
+import CachedIcon from '@material-ui/icons/Cached';
+
+import commonStyles from '../common.styles.css';
+
+export default function ClearNetworkCache(props) {
+ return (
+
+
+ Network Cache
+
+
+
+
+
+ );
+}
diff --git a/desktop-app/app/components/Drawer/index.js b/desktop-app/app/components/Drawer/index.js
index 20d564d3..e74027cb 100644
--- a/desktop-app/app/components/Drawer/index.js
+++ b/desktop-app/app/components/Drawer/index.js
@@ -5,6 +5,7 @@ import cx from 'classnames';
import DeviceDrawerContainer from '../../containers/DeviceDrawerContainer';
import UserPreferencesContainer from '../../containers/UserPreferencesContainer';
import ExtensionsManagerContainer from '../../containers/ExtensionsManagerContainer';
+import NetworkConfigurationContainer from '../../containers/NetworkConfigurationContainer';
import styles from './styles.css';
import commonStyles from '../common.styles.css';
@@ -12,6 +13,7 @@ import {
DEVICE_MANAGER,
USER_PREFERENCES,
EXTENSIONS_MANAGER,
+ NETWORK_CONFIGURATION,
} from '../../constants/DrawerContents';
import {iconsColor} from '../../constants/colors';
import DoubleLeftArrowIcon from '../icons/DoubleLeftArrow';
@@ -70,6 +72,8 @@ function getDrawerContent(type) {
return ;
case EXTENSIONS_MANAGER:
return ;
+ case NETWORK_CONFIGURATION:
+ return ;
default:
return null;
}
diff --git a/desktop-app/app/components/LeftIconsPane/index.js b/desktop-app/app/components/LeftIconsPane/index.js
index b396fb2f..23e20192 100644
--- a/desktop-app/app/components/LeftIconsPane/index.js
+++ b/desktop-app/app/components/LeftIconsPane/index.js
@@ -6,6 +6,7 @@ import DevicesIcon from '@material-ui/icons/Devices';
import SettingsIcon from '@material-ui/icons/Settings';
import PhotoLibraryIcon from '@material-ui/icons/PhotoLibraryOutlined';
import ExtensionIcon from '@material-ui/icons/Extension';
+import NetworkIcon from '../icons/Network';
import cx from 'classnames';
import Logo from '../icons/Logo';
@@ -17,6 +18,7 @@ import {
SCREENSHOT_MANAGER,
USER_PREFERENCES,
EXTENSIONS_MANAGER,
+ NETWORK_CONFIGURATION
} from '../../constants/DrawerContents';
const LeftIconsPane = props => {
@@ -81,6 +83,18 @@ const LeftIconsPane = props => {
+ toggleState(NETWORK_CONFIGURATION)}
+ >
+
+
+
+
+
+
+
+ );
+}
diff --git a/desktop-app/app/components/NetworkConfiguration/styles.css b/desktop-app/app/components/NetworkConfiguration/styles.css
new file mode 100644
index 00000000..5b879604
--- /dev/null
+++ b/desktop-app/app/components/NetworkConfiguration/styles.css
@@ -0,0 +1,4 @@
+.label {
+ font-size: 14px;
+ margin-bottom: 5px;
+}
diff --git a/desktop-app/app/components/NetworkThrottling/ProfileManager/index.js b/desktop-app/app/components/NetworkThrottling/ProfileManager/index.js
new file mode 100644
index 00000000..48aa58e1
--- /dev/null
+++ b/desktop-app/app/components/NetworkThrottling/ProfileManager/index.js
@@ -0,0 +1,230 @@
+import React, {useState} from 'react';
+import cx from 'classnames';
+import Button from '@material-ui/core/Button';
+import Table from '@material-ui/core/Table';
+import TableBody from '@material-ui/core/TableBody';
+import TableCell from '@material-ui/core/TableCell';
+import TableContainer from '@material-ui/core/TableContainer';
+import TableHead from '@material-ui/core/TableHead';
+import TableRow from '@material-ui/core/TableRow';
+import TableFooter from '@material-ui/core/TableFooter';
+import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
+import AddCircleOutlineOutlinedIcon from '@material-ui/icons/AddCircleOutlineOutlined';
+import TextField from '@material-ui/core/TextField';
+import NumberFormat from 'react-number-format';
+import InputAdornment from '@material-ui/core/InputAdornment';
+
+import commonStyles from '../../common.styles.css';
+import styles from './styles.css';
+
+function NumberFormatCustom(props) {
+ const { inputRef, onChange, ...other } = props;
+
+ return (
+
{
+ onChange({
+ target: {
+ name: props.name,
+ value: values.floatValue,
+ },
+ });
+ }}
+ allowNegative={false}
+ decimalScale={0}
+ />
+ );
+}
+
+export default function ProfileManager({
+ profiles,
+ onSave
+}) {
+ const [currentProfiles, updateProfiles] = useState(profiles);
+ const [newElement, setNewElement] = useState({type: 'Custom'});
+ const [editModeRows, toggleEditModeRows] = useState({})
+
+ const newElementIsInvalid = (newElement.title != null && (newElement.title.trim() == "" || newElement.title.length > 20 || currentProfiles.filter(x => x.title === newElement.title).length !== 0));
+
+ const addNewElement = () => {
+ if (!newElementIsInvalid && newElement.title != null) {
+ setNewElement({type: 'Custom'});
+ updateProfiles([...currentProfiles, newElement]);
+ }
+ }
+
+ const removeProfile = (title) => {
+ updateProfiles(currentProfiles.filter(p => p.title !== title));
+ }
+
+ const updateProfile = (title, key, value) => {
+ const profile = currentProfiles.find(x => x.title === title);
+ if (profile != null && profile.type === 'Custom') {
+ profile[key] = value;
+ updateProfiles([...currentProfiles]);
+ }
+ }
+
+ const toggleEditMode = (row) => {
+ if (row.type !== 'Custom') return;
+ editModeRows[row.title] = !editModeRows[row.title];
+ toggleEditModeRows({...editModeRows});
+ }
+
+ return (
+
+
+
+
+
+ Name
+ Download
+ Upload
+ Latency
+
+
+
+
+ {currentProfiles.map((row) => (
+
+ toggleEditMode(row)}>
+ {row.title}
+
+
+ {row.type !== 'Online' &&
+ updateProfile(row.title, 'downloadKps', e.target.value)}
+ fullWidth
+ variant="outlined"
+ placeholder={row.type !== 'Custom' || !editModeRows[row.title] ? "": "(optional)"}
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: Kb/s,
+ }}
+ disabled={row.type !== 'Custom' || !editModeRows[row.title]}
+ />
+ }
+
+
+ {row.type !== 'Online' &&
+ updateProfile(row.title, 'uploadKps', e.target.value)}
+ fullWidth
+ variant="outlined"
+ placeholder={row.type !== 'Custom' || !editModeRows[row.title] ? "": "(optional)"}
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: Kb/s,
+ }}
+ disabled={row.type !== 'Custom' || !editModeRows[row.title]}
+ />
+ }
+
+
+ {row.type !== 'Online' &&
+ updateProfile(row.title, 'latencyMs', e.target.value)}
+ fullWidth
+ variant="outlined"
+ placeholder={row.type !== 'Custom' || !editModeRows[row.title] ? "": "(optional)"}
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: ms,
+ }}
+ disabled={row.type !== 'Custom' || !editModeRows[row.title]}
+ />
+ }
+
+
+ {row.type === 'Custom' && removeProfile(row.title)} />}
+
+
+ ))}
+
+
+
+
+
+
+
+ setNewElement({...newElement, title: e.target.value})}
+ error={newElementIsInvalid}
+ fullWidth
+ variant="outlined"
+ placeholder="New Profile Name"
+ className={cx(styles.titleField)}
+ />
+
+
+ setNewElement({...newElement, downloadKps: e.target.value})}
+ fullWidth
+ variant="outlined"
+ placeholder="(optional)"
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: Kb/s,
+ }}
+ />
+
+
+ setNewElement({...newElement, uploadKps: e.target.value})}
+ fullWidth
+ variant="outlined"
+ placeholder="(optional)"
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: Kb/s,
+ }}
+ />
+
+
+ setNewElement({...newElement, latencyMs: e.target.value})}
+ fullWidth
+ variant="outlined"
+ placeholder="(optional)"
+ InputProps={{
+ inputComponent: NumberFormatCustom,
+ endAdornment: ms,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/desktop-app/app/components/NetworkThrottling/ProfileManager/styles.css b/desktop-app/app/components/NetworkThrottling/ProfileManager/styles.css
new file mode 100644
index 00000000..94fa7993
--- /dev/null
+++ b/desktop-app/app/components/NetworkThrottling/ProfileManager/styles.css
@@ -0,0 +1,58 @@
+.profileManagerContainer {
+ height: 600px;
+ padding: 20px;
+}
+
+.profilesContainer {
+ max-height: 430px;
+ overflow-y: auto;
+ flex-grow: 1;
+}
+
+.profilesHeader > tr > th {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.profilesRow > th,
+td {
+ padding-right: 16px !important;
+}
+
+.profilesRow > th {
+ cursor: default;
+}
+.profilesRow > th.customProfile {
+ cursor: pointer;
+}
+
+.actionIcon {
+ pointer-events: all;
+ cursor: pointer;
+ color: white;
+}
+
+.actionIcon:hover {
+ color: #6075ef;
+}
+
+.saveButton {
+ position: absolute !important;
+ bottom: 25px;
+ right: 25px;
+}
+
+.numericField * {
+ text-align: right;
+ font-size: 14px !important;
+}
+.numericFieldDisabled fieldset {
+ border: 0 !important;
+}
+.numericFieldDisabled input {
+ color: white !important;
+}
+
+.titleField * {
+ font-size: 14px !important;
+}
diff --git a/desktop-app/app/components/NetworkThrottling/index.js b/desktop-app/app/components/NetworkThrottling/index.js
new file mode 100644
index 00000000..65dc8738
--- /dev/null
+++ b/desktop-app/app/components/NetworkThrottling/index.js
@@ -0,0 +1,125 @@
+import React, {useState} from 'react';
+import cx from 'classnames';
+import Button from '@material-ui/core/Button';
+import NetworkCheckIcon from '@material-ui/icons/NetworkCheck';
+import Select from 'react-select';
+import Dialog from '@material-ui/core/Dialog';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogContentText from '@material-ui/core/DialogContentText';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import ProfileManager from './ProfileManager';
+
+import {makeStyles} from '@material-ui/core/styles';
+import commonStyles from '../common.styles.css';
+import styles from './styles.css';
+
+const selectStyles = {
+ control: selectStyles => ({...selectStyles, backgroundColor: '#ffffff10'}),
+ option: (selectStyles, {data, isDisabled, isFocused, isSelected}) => {
+ const color = 'white';
+ return {
+ ...selectStyles,
+ backgroundColor: isDisabled
+ ? null
+ : isSelected
+ ? '#ffffff40'
+ : isFocused
+ ? '#ffffff20'
+ : null,
+ color: 'white',
+
+ ':active': {
+ ...selectStyles[':active'],
+ backgroundColor: !isDisabled && '#ffffff40',
+ },
+ };
+ },
+ input: selectStyles => ({...selectStyles}),
+ placeholder: selectStyles => ({...selectStyles}),
+ singleValue: (selectStyles, {data}) => ({...selectStyles, color: 'white'}),
+ menu: selectStyles => ({...selectStyles, background: '#4b4b4b', zIndex: 100}),
+};
+
+const useStyles = makeStyles(theme => ({
+ appBar: {
+ position: 'relative',
+ },
+ title: {
+ marginLeft: theme.spacing(2),
+ flex: 1,
+ },
+}));
+
+export default function NetworkThrottling({
+ throttling,
+ onActiveThrottlingProfileChanged,
+ onThrottlingProfilesListChanged,
+}) {
+ const [open, setOpen] = useState(false);
+ const closeDialog = () => setOpen(false);
+ const classes = useStyles();
+
+ const selectedIdx = throttling.findIndex(p => p.active);
+ const options = throttling.map(p => {
+ return {
+ value: p.title,
+ label: p.title,
+ };
+ });
+ const selectedOption = options[selectedIdx];
+
+ const onThrottlingProfileChanged = (val) => {
+ if (val.value !== selectedOption.value)
+ onActiveThrottlingProfileChanged(val.value);
+ };
+
+ const saveThrottlingProfiles = (profiles) => {
+ onThrottlingProfilesListChanged(profiles);
+ closeDialog();
+ }
+
+ return (
+
+
+ Network Throttling
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/desktop-app/app/components/NetworkThrottling/styles.css b/desktop-app/app/components/NetworkThrottling/styles.css
new file mode 100644
index 00000000..7f05f7e4
--- /dev/null
+++ b/desktop-app/app/components/NetworkThrottling/styles.css
@@ -0,0 +1,6 @@
+.networkThrottlingIcon {
+ margin-right: 5px;
+}
+.throttlingProfileSelectorContainer {
+ margin-bottom: 20px;
+}
diff --git a/desktop-app/app/components/WebView/index.js b/desktop-app/app/components/WebView/index.js
index 1b9f2683..bec5361f 100644
--- a/desktop-app/app/components/WebView/index.js
+++ b/desktop-app/app/components/WebView/index.js
@@ -28,6 +28,8 @@ import {
DELETE_STORAGE,
ADDRESS_CHANGE,
STOP_LOADING,
+ CLEAR_NETWORK_CACHE,
+ SET_NETWORK_TROTTLING_PROFILE,
} from '../../constants/pubsubEvents';
import {CAPABILITIES} from '../../constants/devices';
@@ -77,6 +79,7 @@ class WebView extends Component {
address: this.props.browser.address,
};
this.subscriptions = [];
+ this.dbg = null;
}
componentDidMount() {
@@ -141,8 +144,18 @@ class WebView extends Component {
)
);
+ this.subscriptions.push(
+ pubsub.subscribe(SET_NETWORK_TROTTLING_PROFILE, this.setNetworkThrottlingProfile)
+ );
+ this.subscriptions.push(
+ pubsub.subscribe(CLEAR_NETWORK_CACHE, this.clearNetworkCache)
+ );
+
this.webviewRef.current.addEventListener('dom-ready', () => {
this.initEventTriggers(this.webviewRef.current);
+ this.dbg = this.getWebContents().debugger;
+ if (!this.dbg.isAttached())
+ this.dbg.attach();
});
if (this.props.transmitNavigatorStatus) {
@@ -248,6 +261,8 @@ class WebView extends Component {
componentWillUnmount() {
this.subscriptions.forEach(pubsub.unsubscribe);
+ if (this.dbg && this.dbg.isAttached())
+ this.dbg.detach();
}
initDeviceEmulationParams = () => {
@@ -397,6 +412,44 @@ class WebView extends Component {
this.webviewRef.current.send('disableInspectorMessage');
};
+ setNetworkThrottlingProfile = ({
+ type,
+ downloadKps,
+ uploadKps,
+ latencyMs,
+ }) => {
+ // TODO : change this when https://github.com/electron/electron/issues/21250 is solved
+ // if (type === 'Online') {
+ // this.getWebContents().session.disableNetworkEmulation();
+ // } else if (type === 'Offline') {
+ // this.getWebContents().session.enableNetworkEmulation({offline: true});
+ // } else if (type === 'Custom') {
+ // const downloadThroughput = downloadKps != null? downloadKps * 128 : undefined;
+ // const uploadThroughput = uploadKps != null? uploadKps * 128 : undefined;
+ // this.getWebContents().session.enableNetworkEmulation({offline: false, latency: latencyMs, downloadThroughput, uploadThroughput });
+ // }
+
+ // WORKAROUND
+ if (type === 'Online') {
+ this.dbg.sendCommand('Network.disable');
+ } else if (type === 'Offline') {
+ this.dbg.sendCommand('Network.enable').then(_ => {
+ this.dbg.sendCommand('Network.emulateNetworkConditions', {offline: true, latency: 0, downloadThroughput: -1, uploadThroughput: -1});
+ });
+ } else {
+ const downloadThroughput = downloadKps != null? downloadKps * 128 : -1;
+ const uploadThroughput = uploadKps != null? uploadKps * 128 : -1;
+ const latency = latencyMs || 0;
+ this.dbg.sendCommand('Network.enable').then(_ => {
+ this.dbg.sendCommand('Network.emulateNetworkConditions', {offline: false, latency, downloadThroughput, uploadThroughput});
+ });
+ }
+ }
+
+ clearNetworkCache = () => {
+ this.getWebContents().session.clearCache();
+ }
+
messageHandler = ({channel: type, args: [message]}) => {
if (type !== MESSAGE_TYPES.toggleEventMirroring && this.state.isUnplugged) {
return;
@@ -442,7 +495,7 @@ class WebView extends Component {
bsScript.src = '${BROWSER_SYNC_EMBED_SCRIPT}';
bsScript.async = true;
document.body.appendChild(bsScript);
-
+
responsivelyApp.deviceId = '${this.props.device.id}';
document.addEventListener('mouseleave', () => {
window.responsivelyApp.mouseOn = false;
diff --git a/desktop-app/app/components/icons/Network.js b/desktop-app/app/components/icons/Network.js
new file mode 100644
index 00000000..aa6b2b8c
--- /dev/null
+++ b/desktop-app/app/components/icons/Network.js
@@ -0,0 +1,41 @@
+import React, {Fragment} from 'react';
+
+export default ({width, height, color, padding, margin}) => (
+
+
+
+);
diff --git a/desktop-app/app/constants/DrawerContents.js b/desktop-app/app/constants/DrawerContents.js
index c6029192..75341c39 100644
--- a/desktop-app/app/constants/DrawerContents.js
+++ b/desktop-app/app/constants/DrawerContents.js
@@ -2,3 +2,4 @@ export const DEVICE_MANAGER = 'DEVICE_MANAGER';
export const SCREENSHOT_MANAGER = 'SCREENSHOT_MANAGER';
export const USER_PREFERENCES = 'USER_PREFERENCES';
export const EXTENSIONS_MANAGER = 'EXTENSIONS_MANAGER';
+export const NETWORK_CONFIGURATION = 'NETWORK_CONFIGURATION';
diff --git a/desktop-app/app/constants/pubsubEvents.js b/desktop-app/app/constants/pubsubEvents.js
index aa3d38e0..eb9925ad 100644
--- a/desktop-app/app/constants/pubsubEvents.js
+++ b/desktop-app/app/constants/pubsubEvents.js
@@ -13,5 +13,8 @@ export const ENABLE_INSPECTOR_ALL_DEVICES = 'ENABLE_INSPECTOR_ALL_DEVICES';
export const DISABLE_INSPECTOR_ALL_DEVICES = 'DISABLE_INSPECTOR_ALL_DEVICES';
export const STOP_LOADING = 'STOP_LOADING';
+export const SET_NETWORK_TROTTLING_PROFILE = 'SET_NETWORK_TROTTLING_PROFILE';
+export const CLEAR_NETWORK_CACHE = 'CLEAR_NETWORK_CACHE';
+
// status bar events
export const STATUS_BAR_VISIBILITY_CHANGE = 'status-bar-visibility-change';
diff --git a/desktop-app/app/constants/settingKeys.js b/desktop-app/app/constants/settingKeys.js
index c585d71e..08be0578 100644
--- a/desktop-app/app/constants/settingKeys.js
+++ b/desktop-app/app/constants/settingKeys.js
@@ -1,5 +1,6 @@
export const ACTIVE_DEVICES = 'activeDevices';
export const CUSTOM_DEVICES = 'customDevices';
export const USER_PREFERENCES = 'userPreferences';
+export const NETWORK_CONFIGURATION = 'networkConfiguration';
export const BOOKMARKS = 'bookmarks';
export const STATUS_BAR_VISIBILITY = 'statusBarVisibility';
diff --git a/desktop-app/app/containers/NetworkConfigurationContainer/index.js b/desktop-app/app/containers/NetworkConfigurationContainer/index.js
new file mode 100644
index 00000000..85da4537
--- /dev/null
+++ b/desktop-app/app/containers/NetworkConfigurationContainer/index.js
@@ -0,0 +1,20 @@
+// @flow
+import React from 'react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+
+import NetworkConfiguration from '../../components/NetworkConfiguration';
+import * as NetworkConfigActions from '../../actions/networkConfig';
+
+function mapStateToProps(state) {
+ return {
+ throttling: state.browser.networkConfiguration.throttling,
+ // proxy: state.browser.networkConfiguration.proxy,
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return bindActionCreators(NetworkConfigActions, dispatch);
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(NetworkConfiguration);
diff --git a/desktop-app/app/reducers/browser.js b/desktop-app/app/reducers/browser.js
index 9ea999c7..da5ce8a2 100644
--- a/desktop-app/app/reducers/browser.js
+++ b/desktop-app/app/reducers/browser.js
@@ -27,6 +27,10 @@ import {
TOGGLE_ALL_DEVICES_MUTED,
TOGGLE_DEVICE_MUTED,
} from '../actions/browser';
+import {
+ CHANGE_ACTIVE_THROTTLING_PROFILE,
+ SAVE_THROTTLING_PROFILES,
+} from '../actions/networkConfig'
import type {Action} from './types';
import getAllDevices from '../constants/devices';
import type {Device} from '../constants/devices';
@@ -40,6 +44,7 @@ import {
ACTIVE_DEVICES,
USER_PREFERENCES,
CUSTOM_DEVICES,
+ NETWORK_CONFIGURATION,
} from '../constants/settingKeys';
import {
getHomepage,
@@ -120,6 +125,20 @@ type FilterFieldType = FILTER_FIELDS.OS | FILTER_FIELDS.DEVICE_TYPE;
type FilterType = {[key: FilterFieldType]: Array};
+type NetworkThrottlingProfileType = {
+ type: 'Online' | 'Offline' | 'Preset' | 'Custom',
+ title: string,
+ downloadKps: number,
+ uploadKps: number,
+ latencyMs: number,
+ active: boolean
+};
+
+type NetworkConfigurationType = {
+ throttling: NetworkThrottlingProfileType[],
+ // proxy: NetworkProxyProfileType[],
+};
+
export type BrowserStateType = {
devices: Array,
homepage: string,
@@ -137,6 +156,7 @@ export type BrowserStateType = {
isInspecting: boolean,
windowSize: WindowSizeType,
allDevicesMuted: boolean,
+ networkConfiguration: NetworkConfigurationType,
};
let _activeDevices = null;
@@ -231,6 +251,54 @@ function _updateFileWatcher(newURL) {
else ipcRenderer.send('stop-watcher');
}
+function getDefaultNetworkThrottlingProfiles(): NetworkThrottlingProfileType[] {
+ return [
+ {
+ type: 'Online',
+ title: 'Online',
+ active: true,
+ },
+ {
+ type: 'Offline',
+ title: 'Offline',
+ downloadKps: 0,
+ uploadKps: 0,
+ latencyMs: 0
+ },
+ // https://github.com/ChromeDevTools/devtools-frontend/blob/4f404fa8beab837367e49f68e29da427361b1f81/front_end/sdk/NetworkManager.js#L251-L265
+ {
+ type: 'Preset',
+ title: 'Slow 3G',
+ downloadKps: 400,
+ uploadKps: 400,
+ latencyMs: 2000
+ },
+ {
+ type: 'Preset',
+ title: 'Fast 3G',
+ downloadKps: 1475,
+ uploadKps: 675,
+ latencyMs: 563
+ }
+ ]
+}
+
+function _getNetworkConfiguration(): NetworkConfigurationType {
+ const ntwrk: NetworkConfigurationType = settings.get(NETWORK_CONFIGURATION) || {};
+
+ if (ntwrk.throttling == null)
+ ntwrk.throttling = getDefaultNetworkThrottlingProfiles();
+
+ // if (ntwrk.proxy == null)
+ // ntwrk.proxy = getDefaultNetworkProxyProfiles();
+
+ return ntwrk;
+}
+
+function _setNetworkConfiguration(networkConfiguration: NetworkConfigurationType) {
+ settings.set(NETWORK_CONFIGURATION, networkConfiguration);
+}
+
export default function browser(
state: BrowserStateType = {
devices: _getActiveDevices(),
@@ -271,6 +339,7 @@ export default function browser(
isInspecting: false,
windowSize: getWindowSize(),
allDevicesMuted: false,
+ networkConfiguration: _getNetworkConfiguration(),
},
action: Action
) {
@@ -387,6 +456,22 @@ export default function browser(
allDevicesMuted: state.devices.every(x => x.isMuted),
devices: [...state.devices],
};
+ case CHANGE_ACTIVE_THROTTLING_PROFILE:
+ const throttling = state.networkConfiguration.throttling
+ const activeProfile = throttling.find(x => x.title === action.title);
+ if (activeProfile != null) {
+ throttling.forEach(x => x.active = false);
+ activeProfile.active = true;
+ }
+ return {...state, networkConfiguration: {...state.networkConfiguration, throttling: [...throttling]}}
+ case SAVE_THROTTLING_PROFILES:
+ action.profiles.forEach(x => x.active = false);
+ action.profiles[0].active = true;
+ _setNetworkConfiguration({
+ ...state.networkConfiguration,
+ throttling: action.profiles,
+ });
+ return {...state, networkConfiguration: {...state.networkConfiguration, throttling: action.profiles}}
default:
return state;
}
diff --git a/desktop-app/package.json b/desktop-app/package.json
index 83505a8b..bc9c14b3 100644
--- a/desktop-app/package.json
+++ b/desktop-app/package.json
@@ -291,6 +291,7 @@
"react-beautiful-dnd": "^11.0.5",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.8",
+ "react-number-format": "^4.4.1",
"react-redux": "^7.1.0",
"react-resizable": "^1.10.1",
"react-router": "^5.0.1",
diff --git a/desktop-app/yarn.lock b/desktop-app/yarn.lock
index fa06a767..50937fdc 100644
--- a/desktop-app/yarn.lock
+++ b/desktop-app/yarn.lock
@@ -13131,6 +13131,13 @@ react-node-resolver@^2.0.1:
resolved "https://registry.yarnpkg.com/react-node-resolver/-/react-node-resolver-2.0.1.tgz#1f0cc83938bf590a1cf42006b23f6b8f68e7b886"
integrity sha512-+PPy/FtAAo5wsLQYMlHkxJ3AMUGL33gpEIx/HBzS8OrcIfacRhGaNVWUJ8bhEbc64en+/bbCNTVZR+pkhqXEbA==
+react-number-format@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-4.4.1.tgz#d5614dd25edfc21ed48b97356213440081437a94"
+ integrity sha512-ZGFMXZ0U7DcmQ3bSZY3FULOA1mfqreT9NIMYZNoa/ouiSgiTQiYA95Uj2KN8ge6BRr+ghA5vraozqWqsHZQw3Q==
+ dependencies:
+ prop-types "^15.7.2"
+
react-redux@^7.0.3, react-redux@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"