From 22a32283b776e2781ef5709fe89a6f1aeafa0b4e Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 25 May 2021 11:43:43 +1000 Subject: [PATCH] WIP Adding country and styling to modals. --- package.json | 2 + stylesheets/_session.scss | 39 ++- ts/components/OnionStatusDialog.tsx | 328 +++++++++++-------------- ts/components/session/ActionsPanel.tsx | 96 ++++---- ts/ip2country.d.ts | 1 + ts/session/onions/index.ts | 8 +- ts/state/ducks/onion.tsx | 61 ++--- yarn.lock | 19 ++ 8 files changed, 267 insertions(+), 287 deletions(-) create mode 100644 ts/ip2country.d.ts diff --git a/package.json b/package.json index 1e75c9e93..aeeae2bc1 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "classnames": "2.2.5", "color": "^3.1.2", "config": "1.28.1", + "country-code-lookup": "^0.0.19", "cross-env": "^6.0.3", "dompurify": "^2.0.7", "electron-is-dev": "^1.1.0", @@ -74,6 +75,7 @@ "fs-extra": "9.0.0", "glob": "7.1.2", "he": "1.2.0", + "ip2country": "^1.0.0", "jquery": "3.3.1", "jsbn": "1.1.0", "libsodium-wrappers": "^0.7.8", diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 7fe697f9e..1521ffeb3 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1200,19 +1200,11 @@ input { // .session-onion-path-wrapper { - .onion-node-list { + .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% - // } + align-items: flex-start; .onion__node { display: flex; @@ -1220,21 +1212,24 @@ input { 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; } + + svg { + animation: glow 1s ease-in-out infinite alternate; + } + } + } + + @keyframes glow { + from { + -webkit-filter: drop-shadow( 0px 0px 3px rgba(0,0,0,0)); + filter: drop-shadow( 0px 0px 3px rgba(0,0,0,0)); + } + to { + -webkit-filter: drop-shadow( 0px 0px 5px rgba(46, 247, 28, 0.7)); + filter: drop-shadow( 0px 0px 5px rgba(68, 236, 2, 0.7)); } } diff --git a/ts/components/OnionStatusDialog.tsx b/ts/components/OnionStatusDialog.tsx index 83462a248..227d0cb63 100644 --- a/ts/components/OnionStatusDialog.tsx +++ b/ts/components/OnionStatusDialog.tsx @@ -13,205 +13,175 @@ 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'; +import { Snode } from '../session/onions'; + +import ip2country from "ip2country"; +import countryLookup from "country-code-lookup"; + +// import ipLocation = require('ip-location'); + + +// interface OnionStatusDialogProps { +// theme: DefaultTheme; +// nodes?: Array; +// onClose: any; +// } + +// export interface IPathNode { +// ip?: string; +// label: string; +// } + +// export const OnionPath = (props: { nodes: IPathNode[]; hasPath: boolean }) => { +// const { nodes, hasPath } = props; + +// return ( +//
+// {nodes.map(node => { +// return OnionPathNode({ hasPath, node }); +// })} +//
+// ); +// }; + +// export const OnionStatusDialog = (props: OnionStatusDialogProps) => { +// const { theme, onClose } = props; + +// const [onionPathAddresses, setOnionPathAddresses] = useState([]); +// const [pathNodes, setPathNodes] = useState([]); +// const [hasPath, setHasPath] = useState(false); + +// const getOnionPathAddresses = () => { +// const onionPathAddresses = getPathNodesIPAddresses(); +// console.log('Current Onion Path - ', onionPathAddresses); +// setOnionPathAddresses(onionPathAddresses); +// }; + +// const buildOnionPath = () => { +// // TODO: Add i18n to onion path +// // Default path values +// let path = [ +// { +// label: 'You', +// }, +// { +// ip: 'Connecting...', +// label: 'Entry Node', +// }, +// { +// ip: 'Connecting...', +// label: 'Service Node', +// }, +// { +// ip: 'Connecting...', +// label: 'Service Node', +// }, +// { +// label: 'Destination', +// }, +// ]; + +// // FIXME call function to check if an onion path exists +// setHasPath(onionPathAddresses.length !== 0); + +// // if there is a onion path, update the addresses +// if (onionPathAddresses.length) { +// onionPathAddresses.forEach((ipAddress, index) => { +// const pathIndex = index + 1; +// path[pathIndex].ip = ipAddress; +// }); +// } +// setPathNodes(path); +// }; + +// useInterval(() => { +// getOnionPathAddresses(); +// }, 1000); + +// useEffect(() => { +// buildOnionPath(); +// }, [onionPathAddresses]); + +// const openFAQPage = () => { +// console.log('Opening FAQ Page'); +// shell.openExternal('https://getsession.org/faq/#onion-routing'); +// }; + +// return ( +// +//
+//
+//

{window.i18n('onionPathIndicatorDescription')}

+//
+ +// + +// +// +// ); +// }; -interface OnionStatusDialogProps { - theme: DefaultTheme; - nodes?: Array; - onClose: any; -} - -export interface IPathNode { - ip?: string; - label: string; -} - -export const OnionPath = (props: { nodes: IPathNode[]; hasPath: boolean }) => { - const { nodes, hasPath } = props; +const OnionPathModalInner = (props: any) => { + const onionPath = useSelector((state: StateType) => state.onionPaths.snodePath); + return ( -
- {nodes.map(node => { - // return OnionPathNode(hasPath, node) - return OnionPathNode({ hasPath, node }); +
+ {onionPath.path.map((snode: Snode, index: number) => { + return ( + <> + + + ); })} + {/* TODO: Destination node maybe pass in light colour maybe changes based on if 3 nodes are connected similar to the action panel light? */} +
); }; -export const OnionStatusDialog = (props: OnionStatusDialogProps) => { - const { theme, onClose } = props; - - const [onionPathAddresses, setOnionPathAddresses] = useState([]); - const [pathNodes, setPathNodes] = useState([]); - const [hasPath, setHasPath] = useState(false); - - const getOnionPathAddresses = () => { - const onionPathAddresses = getPathNodesIPAddresses(); - console.log('Current Onion Path - ', onionPathAddresses); - setOnionPathAddresses(onionPathAddresses); - }; - - const buildOnionPath = () => { - // TODO: Add i18n to onion path - // Default path values - let path = [ - { - label: 'You', - }, - { - ip: 'Connecting...', - label: 'Entry Node', - }, - { - ip: 'Connecting...', - label: 'Service Node', - }, - { - ip: 'Connecting...', - label: 'Service Node', - }, - { - label: 'Destination', - }, - ]; - - // FIXME call function to check if an onion path exists - setHasPath(onionPathAddresses.length !== 0); - - // if there is a onion path, update the addresses - if (onionPathAddresses.length) { - onionPathAddresses.forEach((ipAddress, index) => { - const pathIndex = index + 1; - path[pathIndex].ip = ipAddress; - }); - } - setPathNodes(path); - }; - - useInterval(() => { - getOnionPathAddresses(); - }, 1000); - - useEffect(() => { - buildOnionPath(); - }, [onionPathAddresses]); - - const openFAQPage = () => { - console.log('Opening FAQ Page'); - shell.openExternal('https://getsession.org/faq/#onion-routing'); - }; - - return ( - -
-
-

{window.i18n('onionPathIndicatorDescription')}

-
- - - - - - ); -}; - -// 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
{'Onion' + JSON.stringify(onionPaths)}
; - // } +/** + * Component containing a coloured status light and an adjacent country label. + * @param props + * @returns + */ +export const LabelledStatusLight = (props: any): JSX.Element => { + let { snode, label } = props; - // let connectedNodesCounts = onionPaths.nodes.reduce() - let connectedNodesCount = _.sumBy(onionPaths.nodes, (node: OnionPathNodeType) => { - return node.isConnected ? 1 : 0; - }); - - if (true) { - console.log('@@@', connectedNodesCount); - return ( - // -
- ); + let labelText = label ? label : countryLookup.byIso(ip2country(snode.ip))?.country; + console.log('@@@@ country data:: ', labelText); + if (!labelText) { + labelText = `${snode.ip} - Destination unknown`; + console.log(`@@@@ country data failure on code:: ${ip2country(snode.ip)} and ip ${snode.ip}`); } - return ( -
-
-

- {node && node.label ? {node.label} : null} - {node.ip && ( - <> -
- {node.ip} - - )} -

+
+ + {labelText ? + <> +
{labelText}
+ + : + null + }
- ); -}; - -const OnionPathModalInner = (props: any) => { - const onionPaths = useSelector((state: StateType) => state.onionPaths); - - // let connectedNodesCount = _.sumBy(onionPaths.nodes, (node: OnionPathNodeType) => { - // return node.isConnected ? 1 : 0; - // }) + ) +} +export const OnionNodeLight = (props: any) => { - return ( -
- {/*
*/} - {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 ( - <> -
- {index <= onionPaths.nodes.map.length ? -
- : - null - } - - {node.ip ? -
country
- : - null - } -
- - ); - })} -
- ); -}; +} export const StatusLight = (props: any) => { const [showModal, toggleShowModal] = useState(false); diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 416e99988..02e77c6ef 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -13,7 +13,7 @@ import { hasSyncedInitialConfigurationItem, removeItemById, } from '../../data/data'; -import { OnionPaths } from '../../session/onions'; +import { OnionPaths, Snode, SnodePath } from '../../session/onions'; import { getMessageQueue } from '../../session/sending'; import { clearSessionsAndPreKeys } from '../../util/accountManager'; import { useDispatch, useSelector } from 'react-redux'; @@ -36,11 +36,9 @@ 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 { StatusLight } from '../OnionStatusDialog'; import { StateType } from '../../state/reducer'; import _ from 'lodash'; -import { constants } from 'original-fs'; // tslint:disable-next-line: no-import-side-effect no-submodule-imports @@ -132,23 +130,28 @@ const Section = (props: { type: SectionType; avatarPath?: string; hasOnionPath?: // 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; + // TEST: + if (type === SectionType.PathIndicator) { + const onionState = useSelector((state: StateType) => state.onionPaths); - console.log('@@@@@ is connected color: ', statusColor); + let statusColor = Constants.UI.COLORS.DANGER; + if (!(onionState && onionState.snodePath)) { + return ; + } else { + + const onionSnodePath = onionState.snodePath; + if (onionState && onionSnodePath && onionSnodePath.path.length > 0) { + let onionNodeCount = onionSnodePath.path.length; + statusColor = + onionNodeCount > 2 + ? Constants.UI.COLORS.GREEN + : onionNodeCount > 1 + ? Constants.UI.COLORS.WARNING + : Constants.UI.COLORS.DANGER; + } + } return ; } @@ -256,30 +259,39 @@ export const ActionsPanel = () => { 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)); + // 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)); + + // TEST: Stuff + // let testNode: SnodePath = { + // bad: false, + // path: new Array() + // } + + // dispatch(updateOnionPaths(testNode)); console.log('Is Onion Path found -', hasOnionPath); setHasOnionPath(hasOnionPath); diff --git a/ts/ip2country.d.ts b/ts/ip2country.d.ts new file mode 100644 index 000000000..97eb6b44a --- /dev/null +++ b/ts/ip2country.d.ts @@ -0,0 +1 @@ +declare module 'ip2country'; \ No newline at end of file diff --git a/ts/session/onions/index.ts b/ts/session/onions/index.ts index fa2dc7909..7c82b5401 100644 --- a/ts/session/onions/index.ts +++ b/ts/session/onions/index.ts @@ -6,11 +6,13 @@ import { UserUtils } from '../utils'; import { snodeHttpsAgent } from '../snode_api/onions'; import { allowOnlyOneAtATime } from '../utils/Promise'; +import { updateOnionPaths } from '../../state/ducks/onion'; + export type Snode = SnodePool.Snode; const desiredGuardCount = 3; const minimumGuardCount = 2; -interface SnodePath { +export interface SnodePath { path: Array; bad: boolean; } @@ -61,6 +63,8 @@ export class OnionPaths { goodPaths = this.onionPaths.filter(x => !x.bad); } + window.inboxStore?.dispatch(updateOnionPaths(goodPaths[0])); + const paths = _.shuffle(goodPaths); if (!toExclude) { @@ -101,6 +105,8 @@ export class OnionPaths { log.error('LokiSnodeAPI::getOnionPath - otherPaths no path in', otherPaths[0]); } + // window.inboxStore?.dispatch(updateOnionPaths(otherPaths[0])); + return otherPaths[0].path; } diff --git a/ts/state/ducks/onion.tsx b/ts/state/ducks/onion.tsx index b705b50e1..d7518b0d3 100644 --- a/ts/state/ducks/onion.tsx +++ b/ts/state/ducks/onion.tsx @@ -1,51 +1,23 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -// import { OpenGroupV2InfoJoinable } from '../../opengroup/opengroupV2/ApiUtil'; - -// export type DefaultRoomsState = Array; +import { SnodePath, Snode } from "../../session/onions/index"; export type OnionState = { - nodes: Array; - // ip?: string; - // label?: string; - // isConnected?: boolean; - // isAttemptingConnect?: boolean; -}; - -const initialState: OnionState = { - nodes: new Array(), + // nodes: Array; + // path: SnodePath; + snodePath: SnodePath; }; // const initialState: OnionState = { -// ip: '', -// label: '', -// isConnected: false, -// isAttemptingConnect: false +// // nodes: new Array(), +// nodes: new Array(), // }; -/** - * 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; -}; +const initialState = { + snodePath: { + path: new Array(), + bad: false + } +} /** * This slice is the one holding the default joinable rooms fetched once in a while from the default opengroup v2 server. @@ -54,9 +26,12 @@ const onionSlice = createSlice({ name: 'onionPaths', initialState, reducers: { - updateOnionPaths(state, action: PayloadAction) { - window.log.warn('updating default rooms', action.payload); - return action.payload as OnionState; + // updateOnionPaths(state, action: PayloadAction) { + updateOnionPaths(state, action: PayloadAction) { + console.log('@@@@ dispatching:: ', action); + return { + snodePath: action.payload + } }, }, }); diff --git a/yarn.lock b/yarn.lock index 4a98eab5a..81af79e49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1254,6 +1254,13 @@ asar@0.14.0: mksnapshot "^0.3.0" tmp "0.0.28" +asbycountry@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/asbycountry/-/asbycountry-1.4.1.tgz#9fd71094edbd0d740d67b0cf75c9a6ecf47eea09" + integrity sha512-742TViobYfarc5bJd6l57/i2YURi7g87t6VgzShubyt2it1r0yWWRvezVPcGF4/fTuNnjizgX6dW4ReJhn8vMw== + dependencies: + chalk "^1.1.3" + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -2771,6 +2778,11 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +country-code-lookup@^0.0.19: + version "0.0.19" + resolved "https://registry.yarnpkg.com/country-code-lookup/-/country-code-lookup-0.0.19.tgz#3fbf0192758ecf0d5eee0efbc220d62706c50fd6" + integrity sha512-lpvgdPyj8RuP0CSZhACNf5ueKlLbv/IQUAQfg7yr/qJbFrdcWV7Y+aDN9K/u/bx3MXRfcsjuW+TdIc0AEj7kDw== + crc32-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-2.0.0.tgz#e3cdd3b4df3168dd74e3de3fbbcb7b297fe908f4" @@ -5707,6 +5719,13 @@ ip-regex@^1.0.1: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" integrity sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0= +ip2country@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ip2country/-/ip2country-1.0.0.tgz#dc5b3ffb50862e7b452d43f6cce7e5ddc1b8951d" + integrity sha1-3Fs/+1CGLntFLUP2zOfl3cG4lR0= + dependencies: + asbycountry "^1.2.0" + ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"