WIP: Onion path modal using redux state and refactored modals.

pull/1665/head
Warrick Corfe-Tan 4 years ago
parent 8374cbab08
commit d7b22e13e1

@ -1867,6 +1867,6 @@
"message": "Session hides your IP by bouncing your messages through several Service Nodes in Session's decentralized network. These are the countries your connection is currently being bounced through:"
},
"learnMore": {
"message" : "Learn More"
"message": "Learn More"
}
}

@ -529,7 +529,7 @@
window.showOnionStatusDialog = () => {
appView.showOnionStatusDialog();
}
};
// Set user's launch count.
const prevLaunchCount = window.getSettingValue('launch-count');

@ -129,7 +129,7 @@
showOnionStatusDialog() {
// eslint-disable-next-line no-param-reassign
const theme = this.getThemeObject();
const dialog = new Whisper.OnionStatusDialogView({theme});
const dialog = new Whisper.OnionStatusDialogView({ theme });
this.el.prepend(dialog.el);
},
showResetSessionIdDialog() {

@ -2,37 +2,36 @@
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.OnionStatusDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize({ theme }) {
this.close = this.close.bind(this);
this.theme = theme;
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'onion-status-dialog',
Component: window.Signal.Components.OnionStatusDialog,
props: {
onClose: this.close,
i18n,
theme: this.theme,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
},
});
})();
'use strict';
window.Whisper = window.Whisper || {};
Whisper.OnionStatusDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize({ theme }) {
this.close = this.close.bind(this);
this.theme = theme;
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'onion-status-dialog',
Component: window.Signal.Components.OnionStatusDialog,
props: {
onClose: this.close,
i18n,
theme: this.theme,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
},
});
})();

@ -306,6 +306,43 @@
}
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
padding: 2rem;
background-color: rgba(0, 0, 0, 0.55);
.modal-content {
background-color: white;
max-height: 80%;
height: 80%;
.modal-title {
padding: 0 2.25rem;
height: 5rem;
max-height: 5rem;
text-transform: uppercase;
font-size: 1.8rem;
}
.modal-body {
height: calc(100% - 16rem);
overflow-y: scroll;
padding: 0 2rem;
}
.modal-footer {
height: 5rem;
max-height: 5rem;
padding: 0 2.25rem;
}
}
}
.onion-status-dialog {
.session-modal__header__title {
font-size: $session-font-lg;
@ -365,7 +402,7 @@
}
.red {
background-color: #FF453A;
background-color: #ff453a;
}
}
}

@ -1198,6 +1198,48 @@ input {
}
}
// .session-onion-path-wrapper {
.onion-node-list {
display: flex;
flex-direction: column;
margin: $session-margin-sm;
// .onion-node__vertical-line {
// border-left: 3px solid green;
// left: 50%;
// position: absolute;
// margin-left: -3px;
// top: 0;
// height: 100%
// }
.onion__node {
display: flex;
flex-grow: 1;
align-items: center;
margin: $session-margin-xs;
// unsure
position: relative;
.line {
width: 50%;
height: 100%;
position: absolute;
left: 25%;
top: 40%;
border-left: 3px solid green;
}
* {
margin: $session-margin-sm;
}
}
}
// }
.session-nickname-wrapper {
position: absolute;
height: 100%;

@ -6,83 +6,80 @@ import { getPathNodesIPAddresses } from '../session/onions/onionSend';
import { useInterval } from '../hooks/useInterval';
import classNames from 'classnames';
import _ from 'lodash';
import { getTheme } from '../state/selectors/theme';
import electron from 'electron';
import { useSelector } from 'react-redux';
import { StateType } from '../state/reducer';
import { OnionPathNodeType } from '../state/ducks/onion';
import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon';
import { Constants } from '../session';
const { shell } = electron;
import { SessionWrapperModal } from '../components/session/SessionWrapperModal';
interface OnionStatusDialogProps {
theme: DefaultTheme;
nodes?: Array<string>;
onClose: any;
}
interface IPathNode {
export interface IPathNode {
ip?: string;
label: string;
}
const OnionPath = (props: { nodes: IPathNode[], hasPath: boolean }) => {
export const OnionPath = (props: { nodes: IPathNode[]; hasPath: boolean }) => {
const { nodes, hasPath } = props;
return (
<div className='onionPath'>
{
nodes.map((node) => {
return (
<div className='dotContainer'>
<div className={classNames('dot', hasPath ? 'green' : 'red')}></div>
<p>
<b>{node.label}</b>
{node.ip && (
<>
<br />
{node.ip}
</>
)}
</p>
</div>
)
})
}
<div className="onionPath">
{nodes.map(node => {
// return OnionPathNode(hasPath, node)
return OnionPathNode({ hasPath, node });
})}
</div>
)
}
);
};
export const OnionStatusDialog = (props: OnionStatusDialogProps) => {
const { theme, onClose } = props;
const [onionPathAddresses, setOnionPathAddresses] = useState<string[]>([])
const [pathNodes, setPathNodes] = useState<IPathNode[]>([])
const [hasPath, setHasPath] = useState<boolean>(false)
const [onionPathAddresses, setOnionPathAddresses] = useState<string[]>([]);
const [pathNodes, setPathNodes] = useState<IPathNode[]>([]);
const [hasPath, setHasPath] = useState<boolean>(false);
const getOnionPathAddresses = () => {
const onionPathAddresses = getPathNodesIPAddresses();
console.log("Current Onion Path - ", onionPathAddresses);
setOnionPathAddresses(onionPathAddresses)
}
console.log('Current Onion Path - ', onionPathAddresses);
setOnionPathAddresses(onionPathAddresses);
};
const buildOnionPath = () => {
// TODO: Add i18n to onion path
// Default path values
let path = [
{
label: 'You'
label: 'You',
},
{
ip: 'Connecting...',
label: 'Entry Node'
label: 'Entry Node',
},
{
ip: 'Connecting...',
label: 'Service Node'
label: 'Service Node',
},
{
ip: 'Connecting...',
label: 'Service Node'
label: 'Service Node',
},
{
label: 'Destination'
label: 'Destination',
},
]
];
// FIXME call function to check if an onion path exists
setHasPath(onionPathAddresses.length !== 0);
@ -92,32 +89,28 @@ export const OnionStatusDialog = (props: OnionStatusDialogProps) => {
onionPathAddresses.forEach((ipAddress, index) => {
const pathIndex = index + 1;
path[pathIndex].ip = ipAddress;
})
});
}
setPathNodes(path);
}
};
useInterval(() => {
getOnionPathAddresses()
}, 1000)
getOnionPathAddresses();
}, 1000);
useEffect(() => {
buildOnionPath()
}, [onionPathAddresses])
buildOnionPath();
}, [onionPathAddresses]);
const openFAQPage = () => {
console.log("Opening FAQ Page")
console.log('Opening FAQ Page');
shell.openExternal('https://getsession.org/faq/#onion-routing');
}
};
return (
<SessionModal
title={window.i18n('onionPathIndicatorTitle')}
theme={theme}
onClose={onClose}
>
<SessionModal title={window.i18n('onionPathIndicatorTitle')} theme={theme} onClose={onClose}>
<div className="spacer-sm" />
<div className='onionDescriptionContainer'>
<div className="onionDescriptionContainer">
<p>{window.i18n('onionPathIndicatorDescription')}</p>
</div>
@ -131,4 +124,119 @@ export const OnionStatusDialog = (props: OnionStatusDialogProps) => {
/>
</SessionModal>
);
}
};
// export const OnionPathNode = (hasPath: boolean, node: IPathNode): JSX.Element => {
// export const OnionPathNode = (hasPath: boolean, node: any): JSX.Element => {
// export const OnionPathNode = (hasPath: boolean, node: any) => {
// export const OnionPathNode = (hasPath: any, node: any) => {
export const OnionPathNode = (props: any) => {
const { hasPath, node } = props;
const theme = useSelector(getTheme);
console.log('@@@ onionpathnode theme', theme);
const onionPaths = useSelector((state: StateType) => state.onionPaths);
console.log('@@@ state onion path node', onionPaths);
// if (!(node && node.label && node.ip)) {
// return <div>{'Onion' + JSON.stringify(onionPaths)}</div>;
// }
// let connectedNodesCounts = onionPaths.nodes.reduce()
let connectedNodesCount = _.sumBy(onionPaths.nodes, (node: OnionPathNodeType) => {
return node.isConnected ? 1 : 0;
});
if (true) {
console.log('@@@', connectedNodesCount);
return (
// <SessionIconButton
// />
<div className="idk"></div>
);
}
return (
<div className="dotContainer">
<div className={classNames('dot', hasPath ? 'green' : 'red')}></div>
<p>
{node && node.label ? <b>{node.label}</b> : null}
{node.ip && (
<>
<br />
{node.ip}
</>
)}
</p>
</div>
);
};
const OnionPathModalInner = (props: any) => {
const onionPaths = useSelector((state: StateType) => state.onionPaths);
// let connectedNodesCount = _.sumBy(onionPaths.nodes, (node: OnionPathNodeType) => {
// return node.isConnected ? 1 : 0;
// })
return (
<div className="onion-node-list">
{/* <div className="onion-node__vertical-line"></div> */}
{onionPaths.nodes.map((node: OnionPathNodeType, index: number) => {
let nodeStatusColor = node.isConnected
? Constants.UI.COLORS.GREEN
: node.isAttemptingConnect
? Constants.UI.COLORS.WARNING
: Constants.UI.COLORS.DANGER;
return (
<>
<div className="onion__node">
{index <= onionPaths.nodes.map.length ?
<div className="line"></div>
:
null
}
<StatusLight color={nodeStatusColor}></StatusLight>
{node.ip ?
<div className="onion-node__country">country</div>
:
null
}
</div>
</>
);
})}
</div>
);
};
export const StatusLight = (props: any) => {
const [showModal, toggleShowModal] = useState(false);
const { isSelected, color } = props;
const theme = useSelector(getTheme);
const onClick = () => {
toggleShowModal(!showModal);
};
return (
<>
<SessionIconButton
iconSize={SessionIconSize.Small}
iconType={SessionIconType.Circle}
iconColor={color}
theme={theme}
isSelected={isSelected}
onClick={onClick}
/>
{showModal ? (
<SessionWrapperModal onclick={onClick} showModal={showModal}>
<OnionPathModalInner></OnionPathModalInner>
</SessionWrapperModal>
) : null}
</>
);
};

@ -36,6 +36,11 @@ import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
import { SwarmPolling } from '../../session/snode_api/swarmPolling';
import { getOnionPathStatus } from '../../session/onions/onionSend';
import { Constants } from '../../session';
import { IPathNode, OnionPathNode, StatusLight } from '../OnionStatusDialog';
import { OnionUpdate, updateOnionPaths, OnionPathNodeType } from '../../state/ducks/onion';
import { StateType } from '../../state/reducer';
import _ from 'lodash';
import { constants } from 'original-fs';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports
@ -73,8 +78,7 @@ const Section = (props: { type: SectionType; avatarPath?: string; hasOnionPath?:
} else if (type === SectionType.PathIndicator) {
// Show Path Indicator Modal
window.showOnionStatusDialog();
}
else {
} else {
dispatch(clearSearch());
dispatch(showLeftPaneSection(type));
}
@ -99,10 +103,12 @@ const Section = (props: { type: SectionType; avatarPath?: string; hasOnionPath?:
let iconColor = undefined;
if (type === SectionType.PathIndicator) {
// Set icon color based on result
iconColor = hasOnionPath ? Constants.UI.COLORS.GREEN : Constants.UI.COLORS.DANGER
console.log("Status Indicator Color", iconColor)
iconColor = hasOnionPath ? Constants.UI.COLORS.GREEN : Constants.UI.COLORS.DANGER;
console.log('Status Indicator Color', iconColor);
}
const unreadToShow = type === SectionType.Message ? unreadMessageCount : undefined;
let iconType: SessionIconType;
switch (type) {
case SectionType.Message:
@ -124,7 +130,28 @@ const Section = (props: { type: SectionType; avatarPath?: string; hasOnionPath?:
iconType = SessionIconType.Moon;
}
const unreadToShow = type === SectionType.Message ? unreadMessageCount : undefined;
// calculate light status.
// TODO: Refactor this so this logic is reusable elsewhere.
if (type === SectionType.PathIndicator) {
const onionPaths = useSelector((state: StateType) => state.onionPaths);
console.log('@@@ state onion path node', onionPaths);
let connectedNodesCount = _.sumBy(onionPaths.nodes, (node: OnionPathNodeType) => {
return node.isConnected ? 1 : 0;
});
console.log('@@@@@ is connected count: ', connectedNodesCount);
const statusColor =
connectedNodesCount > 2
? Constants.UI.COLORS.GREEN
: connectedNodesCount > 1
? Constants.UI.COLORS.WARNING
: Constants.UI.COLORS.DANGER;
console.log('@@@@@ is connected color: ', statusColor);
return <StatusLight isSelected={isSelected} color={statusColor}></StatusLight>;
}
return (
<SessionIconButton
@ -167,8 +194,6 @@ const triggerSyncIfNeeded = async () => {
}
};
/**
* This function is called only once: on app startup with a logged in user
*/
@ -211,7 +236,7 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
export const ActionsPanel = () => {
const dispatch = useDispatch();
const [startCleanUpMedia, setStartCleanUpMedia] = useState(false);
const [hasOnionPath, setHasOnionPath] = useState<boolean>(false)
const [hasOnionPath, setHasOnionPath] = useState<boolean>(false);
const ourPrimaryConversation = useSelector(getOurPrimaryConversation);
// this maxi useEffect is called only once: when the component is mounted.
@ -228,11 +253,37 @@ export const ActionsPanel = () => {
return () => global.clearTimeout(timeout);
}, []);
const getOnionPathIndicator = () => {
const hasOnionPath = getOnionPathStatus();
console.log("Is Onion Path found -", hasOnionPath)
setHasOnionPath(hasOnionPath)
}
const getOnionPathIndicator = () => {
const hasOnionPath = getOnionPathStatus();
const update: OnionUpdate = {
nodes: [
{
ip: 'hi',
label: 'hi',
isConnected: Math.random() > 0.5,
isAttemptingConnect: Math.random() > 0.7,
},
{
ip: 'hi2',
label: 'hi2',
isConnected: Math.random() > 0.5,
isAttemptingConnect: Math.random() > 0.7,
},
{
ip: 'hi3',
label: 'hi3',
isConnected: Math.random() > 0.5,
isAttemptingConnect: Math.random() > 0.7,
},
],
};
dispatch(updateOnionPaths(update));
console.log('Is Onion Path found -', hasOnionPath);
setHasOnionPath(hasOnionPath);
};
useInterval(() => {
getOnionPathIndicator();

@ -0,0 +1,164 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon/';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
import { DefaultTheme } from 'styled-components';
interface Props {
title: string;
onClose: any;
showExitIcon?: boolean;
showHeader?: boolean;
headerReverse?: boolean;
//Maximum of two icons or buttons in header
headerIconButtons?: Array<{
iconType: SessionIconType;
iconRotation: number;
onClick?: any;
}>;
headerButtons?: Array<{
buttonType: SessionButtonType;
buttonColor: SessionButtonColor;
text: string;
onClick?: any;
}>;
theme: DefaultTheme;
}
interface State {
isVisible: boolean;
}
export const SessionWrapperModal = (props: any) => {
const { onclick, showModal } = props;
return (
<div className="loki-dialog session-confirm-wrapper modal">
<div className="session-confirm-wrapper">
<div className="session-modal">
<div className="session-modal__header">
<div className="session-modal__header__title">
Onion Nodes / Generic Title
</div>
</div>
<div className="session-modal__body">
<div className="session-modal__centered">
{props.children}
<div className="session-modal__button-group">
<SessionButton onClick={props.onclick}>Close</SessionButton>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
// export class SessionModal extends React.PureComponent<Props, State> {
// public static defaultProps = {
// showExitIcon: true,
// showHeader: true,
// headerReverse: false,
// };
// private node: HTMLDivElement | null;
// constructor(props: any) {
// super(props);
// this.state = {
// isVisible: true,
// };
// this.close = this.close.bind(this);
// this.onKeyUp = this.onKeyUp.bind(this);
// this.node = null;
// window.addEventListener('keyup', this.onKeyUp);
// }
// public componentDidMount() {
// document.addEventListener('mousedown', this.handleClick, false);
// }
// public componentWillUnmount() {
// document.removeEventListener('mousedown', this.handleClick, false);
// }
// public handleClick = (e: any) => {
// if (this.node && this.node.contains(e.target)) {
// return;
// }
// this.close();
// };
// public render() {
// const { title, headerIconButtons, showExitIcon, showHeader, headerReverse } = this.props;
// const { isVisible } = this.state;
// return isVisible ? (
// <div ref={node => (this.node = node)} className={'session-modal'}>
// {showHeader ? (
// <>
// <div className={classNames('session-modal__header', headerReverse && 'reverse')}>
// <div className="session-modal__header__close">
// {showExitIcon ? (
// <SessionIconButton
// iconType={SessionIconType.Exit}
// iconSize={SessionIconSize.Small}
// onClick={this.close}
// theme={this.props.theme}
// />
// ) : null}
// </div>
// <div className="session-modal__header__title">{title}</div>
// <div className="session-modal__header__icons">
// {headerIconButtons
// ? headerIconButtons.map((iconItem: any) => {
// return (
// <SessionIconButton
// key={iconItem.iconType}
// iconType={iconItem.iconType}
// iconSize={SessionIconSize.Large}
// iconRotation={iconItem.iconRotation}
// onClick={iconItem.onClick}
// theme={this.props.theme}
// />
// );
// })
// : null}
// </div>
// </div>
// </>
// ) : null}
// <div className="session-modal__body">{this.props.children}</div>
// </div>
// ) : null;
// }
// public close() {
// this.setState({
// isVisible: false,
// });
// window.removeEventListener('keyup', this.onKeyUp);
// document.removeEventListener('mousedown', this.handleClick, false);
// if (this.props.onClose) {
// this.props.onClose();
// }
// }
// public onKeyUp(event: any) {
// switch (event.key) {
// case 'Esc':
// case 'Escape':
// this.close();
// break;
// default:
// }
// }
// }

@ -41,6 +41,9 @@ export const UI = {
WHITE_PALE: '#AFAFAF',
GREEN: '#00F782',
// CAUTION
WARNING: '#FFC02E',
// SEMANTIC COLORS
DANGER: '#FF453A',
DANGER_ALT: '#FF4538',

@ -198,6 +198,8 @@ export class OnionPaths {
let guardNodes: Array<Snode> = [];
console.log('@@@@ guardNodes: ', guardNodes);
// The use of await inside while is intentional:
// we only want to repeat if the await fails
// eslint-disable-next-line-no-await-in-loop

@ -133,7 +133,7 @@ export const getOnionPathStatus = () => {
export const getPathNodesIPAddresses = () => {
let pathNodes: Array<Snode> = [];
let displayNode: Array<string> = []
let displayNode: Array<string> = [];
try {
pathNodes = OnionPaths.getInstance().getOnionPathNoRebuild();
displayNode = pathNodes.map(node => node.ip);

@ -0,0 +1,67 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// import { OpenGroupV2InfoJoinable } from '../../opengroup/opengroupV2/ApiUtil';
// export type DefaultRoomsState = Array<OpenGroupV2InfoJoinable>;
export type OnionState = {
nodes: Array<OnionPathNodeType>;
// ip?: string;
// label?: string;
// isConnected?: boolean;
// isAttemptingConnect?: boolean;
};
const initialState: OnionState = {
nodes: new Array<OnionPathNodeType>(),
};
// const initialState: OnionState = {
// ip: '',
// label: '',
// isConnected: false,
// isAttemptingConnect: false
// };
/**
* Payload to dispatch to update the base64 data of a default room
*/
export type Base64Update = {
base64Data: string;
roomId: string;
};
/**
* Type for a singular onion node to be used in the onion redux state.
*/
export type OnionPathNodeType = {
ip?: string;
label?: string;
isConnected?: boolean;
isAttemptingConnect?: boolean;
};
/**
* Payload to dispatch an update of the onion node paths
*/
export type OnionUpdate = {
nodes: Array<OnionPathNodeType>;
};
/**
* This slice is the one holding the default joinable rooms fetched once in a while from the default opengroup v2 server.
*/
const onionSlice = createSlice({
name: 'onionPaths',
initialState,
reducers: {
updateOnionPaths(state, action: PayloadAction<OnionUpdate>) {
window.log.warn('updating default rooms', action.payload);
return action.payload as OnionState;
},
},
});
// destructures
const { actions, reducer } = onionSlice;
export const { updateOnionPaths } = actions;
export const defaultOnionReducer = reducer;

@ -7,6 +7,8 @@ import { reducer as theme, ThemeStateType } from './ducks/theme';
import { reducer as section, SectionStateType } from './ducks/section';
import { defaultRoomReducer as defaultRooms, DefaultRoomsState } from './ducks/defaultRooms';
import { defaultOnionReducer as onionPaths, OnionState } from './ducks/onion';
export type StateType = {
search: SearchStateType;
// messages: MessagesStateType;
@ -15,6 +17,8 @@ export type StateType = {
theme: ThemeStateType;
section: SectionStateType;
defaultRooms: DefaultRoomsState;
onionPaths: OnionState;
};
export const reducers = {
@ -27,6 +31,8 @@ export const reducers = {
theme,
section,
defaultRooms,
onionPaths,
};
// Making this work would require that our reducer signature supported AnyAction, not

Loading…
Cancel
Save