From e85f69a144e5e0dc1296382380334f02c70b2b09 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 29 Jun 2021 13:48:43 +1000 Subject: [PATCH 1/3] use our retrieve status as isOnline status --- js/background.js | 1 - ts/components/LeftPane.tsx | 3 -- ts/components/OnionStatusPathDialog.tsx | 8 ++--- .../session/network/SessionOffline.tsx | 36 ------------------- ts/session/onions/onionSend.ts | 2 +- ts/session/snode_api/SNodeAPI.ts | 11 ++++++ ts/session/snode_api/onions.ts | 8 ++++- ts/session/snode_api/swarmPolling.ts | 12 ++----- ts/state/ducks/onion.tsx | 9 +++-- ts/state/selectors/onions.ts | 5 +++ 10 files changed, 37 insertions(+), 58 deletions(-) delete mode 100644 ts/components/session/network/SessionOffline.tsx diff --git a/js/background.js b/js/background.js index 558c143c6..c58986015 100644 --- a/js/background.js +++ b/js/background.js @@ -450,7 +450,6 @@ }, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000); window.NewReceiver.queueAllCached(); - window.libsession.Utils.AttachmentDownloads.start({ logger: window.log, }); diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 4b5e4cfb0..da5a16129 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -7,7 +7,6 @@ import { openConversationExternal } from '../state/ducks/conversations'; import { LeftPaneContactSection } from './session/LeftPaneContactSection'; import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; import { SessionTheme } from '../state/ducks/SessionTheme'; -import { SessionOffline } from './session/network/SessionOffline'; import { SessionExpiredWarning } from './session/network/SessionExpiredWarning'; import { getFocusedSection } from '../state/selectors/section'; import { useDispatch, useSelector } from 'react-redux'; @@ -44,7 +43,6 @@ const InnerLeftPaneMessageSection = (props: { isExpired: boolean }) => { return ( <> - {props.isExpired && } { return ( <> - dispatch(openConversationExternal(id, messageId)) diff --git a/ts/components/OnionStatusPathDialog.tsx b/ts/components/OnionStatusPathDialog.tsx index 72c386aed..1fef5ad03 100644 --- a/ts/components/OnionStatusPathDialog.tsx +++ b/ts/components/OnionStatusPathDialog.tsx @@ -20,11 +20,10 @@ import { onionPathModal } from '../state/ducks/modalDialog'; import { getFirstOnionPath, getFirstOnionPathLength, + getIsOnline, getOnionPathsCount, } from '../state/selectors/onions'; -// tslint:disable-next-line: no-submodule-imports -import useNetworkState from 'react-use/lib/useNetworkState'; import { SessionSpinner } from './session/SessionSpinner'; import { Flex } from './basic/Flex'; @@ -36,9 +35,10 @@ export type StatusLightType = { const OnionPathModalInner = () => { const onionPath = useSelector(getFirstOnionPath); + const isOnline = useSelector(getIsOnline); // including the device and destination in calculation const glowDuration = onionPath.length + 2; - if (!onionPath || onionPath.length === 0) { + if (!isOnline || !onionPath || onionPath.length === 0) { return ; } @@ -144,7 +144,7 @@ export const ActionPanelOnionStatusLight = (props: { const theme = useTheme(); const onionPathsCount = useSelector(getOnionPathsCount); const firstPathLength = useSelector(getFirstOnionPathLength); - const isOnline = useNetworkState().online; + const isOnline = useSelector(getIsOnline); // Set icon color based on result const red = theme.colors.destructive; diff --git a/ts/components/session/network/SessionOffline.tsx b/ts/components/session/network/SessionOffline.tsx deleted file mode 100644 index 3dbb4a6b4..000000000 --- a/ts/components/session/network/SessionOffline.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; - -// tslint:disable-next-line: no-submodule-imports -import useNetworkState from 'react-use/lib/useNetworkState'; -import styled from 'styled-components'; - -type ContainerProps = { - show: boolean; -}; - -const OfflineContainer = styled.div` - background: ${props => props.theme.colors.accent}; - color: ${props => props.theme.colors.textColor}; - padding: ${props => (props.show ? props.theme.common.margins.sm : '0px')}; - margin: ${props => (props.show ? props.theme.common.margins.xs : '0px')}; - height: ${props => (props.show ? 'auto' : '0px')}; - overflow: hidden; - transition: ${props => props.theme.common.animations.defaultDuration}; -`; - -const OfflineTitle = styled.h3` - padding-top: 0px; - margin-top: 0px; -`; - -const OfflineMessage = styled.div``; - -export const SessionOffline = () => { - const isOnline = useNetworkState().online; - return ( - - {window.i18n('offline')} - {window.i18n('checkNetworkConnection')} - - ); -}; diff --git a/ts/session/onions/onionSend.ts b/ts/session/onions/onionSend.ts index 3af4cd57d..934a18731 100644 --- a/ts/session/onions/onionSend.ts +++ b/ts/session/onions/onionSend.ts @@ -179,7 +179,7 @@ export const sendViaOnion = async ( { retries: 9, // each path can fail 3 times before being dropped, we have 3 paths at most factor: 2, - minTimeout: 1000, + minTimeout: 100, maxTimeout: 4000, onFailedAttempt: e => { window?.log?.warn( diff --git a/ts/session/snode_api/SNodeAPI.ts b/ts/session/snode_api/SNodeAPI.ts index 8df50f5db..bc25631d0 100644 --- a/ts/session/snode_api/SNodeAPI.ts +++ b/ts/session/snode_api/SNodeAPI.ts @@ -24,11 +24,14 @@ import { toHex, } from '../utils/String'; import { Snode } from '../../data/data'; +import { updateIsOnline } from '../../state/ducks/onion'; // ONS name can have [a-zA-Z0-9_-] except that - is not allowed as start or end // do not define a regex but rather create it on the fly to avoid https://stackoverflow.com/questions/3891641/regex-test-only-works-every-other-time export const onsNameRegex = '^\\w([\\w-]*[\\w])?$'; +export const ERROR_CODE_NO_CONNECT = 'ENETUNREACH: No network connection.'; + const getSslAgentForSeedNode = (seedNodeHost: string, isSsl = false) => { let filePrefix = ''; let pubkey256 = ''; @@ -527,9 +530,12 @@ export async function retrieveNextMessages( try { const json = JSON.parse(result.body); + window.inboxStore?.dispatch(updateIsOnline(true)); + return json.messages || []; } catch (e) { window?.log?.warn('exception while parsing json of nextMessage:', e); + window.inboxStore?.dispatch(updateIsOnline(true)); return []; } @@ -538,6 +544,11 @@ export async function retrieveNextMessages( 'Got an error while retrieving next messages. Not retrying as we trigger fetch often:', e ); + if (e.message === ERROR_CODE_NO_CONNECT) { + window.inboxStore?.dispatch(updateIsOnline(false)); + } else { + window.inboxStore?.dispatch(updateIsOnline(true)); + } return []; } } diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index f8c4b7124..1c508fefd 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -13,6 +13,7 @@ import { hrefPnServerDev, hrefPnServerProd } from '../../pushnotification/PnServ let snodeFailureCount: Record = {}; import { Snode } from '../../data/data'; +import { ERROR_CODE_NO_CONNECT } from './SNodeAPI'; // tslint:disable-next-line: variable-name export const TEST_resetSnodeFailureCount = () => { @@ -783,6 +784,7 @@ const sendOnionRequest = async ({ // we are talking to a snode... agent: snodeHttpsAgent, abortSignal, + timeout: 5000, }; const guardUrl = `https://${guardNode.ip}:${guardNode.port}/onion_req/v2`; @@ -859,7 +861,7 @@ export async function lokiOnionFetch( return onionFetchRetryable(targetNode, body, associatedWith); }, { - retries: 9, + retries: 4, factor: 1, minTimeout: 1000, maxTimeout: 2000, @@ -875,6 +877,10 @@ export async function lokiOnionFetch( } catch (e) { window?.log?.warn('onionFetchRetryable failed ', e); // console.warn('error to show to user'); + if (e?.errno === 'ENETUNREACH') { + // better handle the no connection state + throw new Error(ERROR_CODE_NO_CONNECT); + } throw e; } } diff --git a/ts/session/snode_api/swarmPolling.ts b/ts/session/snode_api/swarmPolling.ts index 87239c427..40a45c194 100644 --- a/ts/session/snode_api/swarmPolling.ts +++ b/ts/session/snode_api/swarmPolling.ts @@ -51,7 +51,6 @@ export const getSwarmPollingInstance = () => { }; export class SwarmPolling { - private ourPubkey: PubKey | undefined; private groupPolling: Array<{ pubkey: PubKey; lastPolledTimestamp: number }>; private readonly lastHashes: { [key: string]: PubkeyToHash }; @@ -61,7 +60,6 @@ export class SwarmPolling { } public async start(waitForFirstPoll = false): Promise { - this.ourPubkey = UserUtils.getOurPubKeyFromCache(); this.loadGroupIds(); if (waitForFirstPoll) { await this.TEST_pollForAllKeys(); @@ -74,7 +72,6 @@ export class SwarmPolling { * Used fo testing only */ public TEST_reset() { - this.ourPubkey = undefined; this.groupPolling = []; } @@ -88,10 +85,6 @@ export class SwarmPolling { public removePubkey(pk: PubKey | string) { const pubkey = PubKey.cast(pk); window?.log?.info('Swarm removePubkey: removing pubkey from polling', pubkey.key); - - if (this.ourPubkey && PubKey.cast(pk).isEqual(this.ourPubkey)) { - this.ourPubkey = undefined; - } this.groupPolling = this.groupPolling.filter(group => !pubkey.isEqual(group.pubkey)); } @@ -132,9 +125,8 @@ export class SwarmPolling { */ public async TEST_pollForAllKeys() { // we always poll as often as possible for our pubkey - const directPromise = this.ourPubkey - ? this.TEST_pollOnceForKey(this.ourPubkey, false) - : Promise.resolve(); + const ourPubkey = UserUtils.getOurPubKeyFromCache(); + const directPromise = this.TEST_pollOnceForKey(ourPubkey, false); const now = Date.now(); const groupPromises = this.groupPolling.map(async group => { diff --git a/ts/state/ducks/onion.tsx b/ts/state/ducks/onion.tsx index 2c607adf3..4ebedebf2 100644 --- a/ts/state/ducks/onion.tsx +++ b/ts/state/ducks/onion.tsx @@ -3,10 +3,12 @@ import { Snode } from '../../data/data'; export type OnionState = { snodePaths: Array>; + isOnline: boolean; }; export const initialOnionPathState = { snodePaths: new Array>(), + isOnline: false, }; /** @@ -17,12 +19,15 @@ const onionSlice = createSlice({ initialState: initialOnionPathState, reducers: { updateOnionPaths(state: OnionState, action: PayloadAction>>) { - return { snodePaths: action.payload }; + return { ...state, snodePaths: action.payload }; + }, + updateIsOnline(state: OnionState, action: PayloadAction) { + return { ...state, isOnline: action.payload }; }, }, }); // destructures const { actions, reducer } = onionSlice; -export const { updateOnionPaths } = actions; +export const { updateOnionPaths, updateIsOnline } = actions; export const defaultOnionReducer = reducer; diff --git a/ts/state/selectors/onions.ts b/ts/state/selectors/onions.ts index 1e3d95a0d..897e8b3ff 100644 --- a/ts/state/selectors/onions.ts +++ b/ts/state/selectors/onions.ts @@ -21,3 +21,8 @@ export const getFirstOnionPathLength = createSelector( getFirstOnionPath, (state: Array): number => state.length || 0 ); + +export const getIsOnline = createSelector( + getOnionPaths, + (state: OnionState): boolean => state.isOnline +); From 504a9afc0aa61a09242cd117c0ae62d7a9f37a49 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 29 Jun 2021 14:55:59 +1000 Subject: [PATCH 2/3] fix up handling of clock out of sync --- ts/models/message.ts | 2 +- ts/session/sending/LokiMessageApi.ts | 10 +++++----- ts/session/snode_api/SNodeAPI.ts | 2 +- ts/session/snode_api/onions.ts | 10 ++++++++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ts/models/message.ts b/ts/models/message.ts index d86455acb..339058372 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -846,7 +846,7 @@ export class MessageModel extends Backbone.Model { const chatParams = { identifier: this.id, body, - timestamp: this.get('sent_at') || Date.now(), + timestamp: Date.now(), // force a new timestamp to handle user fixed his clock expireTimer: this.get('expireTimer'), attachments, preview, diff --git a/ts/session/sending/LokiMessageApi.ts b/ts/session/sending/LokiMessageApi.ts index eb5d7863e..d2ecbb800 100644 --- a/ts/session/sending/LokiMessageApi.ts +++ b/ts/session/sending/LokiMessageApi.ts @@ -70,11 +70,11 @@ export async function sendMessage( ); throw e; } - if (!snode) { + if (!usedNodes || usedNodes.length === 0) { throw new window.textsecure.EmptySwarmError(pubKey, 'Ran out of swarm nodes to query'); - } else { - window?.log?.info( - `loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}` - ); } + + window?.log?.info( + `loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}` + ); } diff --git a/ts/session/snode_api/SNodeAPI.ts b/ts/session/snode_api/SNodeAPI.ts index bc25631d0..911d4a8d3 100644 --- a/ts/session/snode_api/SNodeAPI.ts +++ b/ts/session/snode_api/SNodeAPI.ts @@ -496,8 +496,8 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis e, `destination ${targetNode.ip}:${targetNode.port}` ); + throw e; } - return false; } /** */ diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index 1c508fefd..f73c99acf 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -38,6 +38,9 @@ export interface SnodeResponse { export const NEXT_NODE_NOT_FOUND_PREFIX = 'Next node not found: '; +export const CLOCK_OUT_OF_SYNC_MESSAGE_ERROR = + 'You clock is out of sync with the network. Check your clock'; + // Returns the actual ciphertext, symmetric key that will be used // for decryption, and an ephemeral_key to send to the next hop async function encryptForPubKey(pubKeyX25519hex: string, reqObj: any): Promise { @@ -196,9 +199,8 @@ async function buildOnionGuardNodePayload( function process406Error(statusCode: number) { if (statusCode === 406) { // clock out of sync - console.warn('clock out of sync todo'); // this will make the pRetry stop - throw new pRetry.AbortError('You clock is out of sync with the network. Check your clock.'); + throw new pRetry.AbortError(CLOCK_OUT_OF_SYNC_MESSAGE_ERROR); } } @@ -881,6 +883,10 @@ export async function lokiOnionFetch( // better handle the no connection state throw new Error(ERROR_CODE_NO_CONNECT); } + if (e?.message === CLOCK_OUT_OF_SYNC_MESSAGE_ERROR) { + window?.log?.warn('Its an clock out of sync error '); + throw new pRetry.AbortError(CLOCK_OUT_OF_SYNC_MESSAGE_ERROR); + } throw e; } } From 447f862ace9986d7bf9f66960ef9c567dd102a30 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 29 Jun 2021 15:20:21 +1000 Subject: [PATCH 3/3] add some static glowing to the actionpanel light --- ts/components/OnionStatusPathDialog.tsx | 3 +++ ts/components/session/icon/SessionIcon.tsx | 27 ++++++++++++++----- .../session/icon/SessionIconButton.tsx | 6 +++++ ts/session/snode_api/onions.ts | 2 +- .../session/unit/onion/OnionErrors_test.ts | 4 +-- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/ts/components/OnionStatusPathDialog.tsx b/ts/components/OnionStatusPathDialog.tsx index 1fef5ad03..a7099318e 100644 --- a/ts/components/OnionStatusPathDialog.tsx +++ b/ts/components/OnionStatusPathDialog.tsx @@ -164,6 +164,9 @@ export const ActionPanelOnionStatusLight = (props: { iconType={SessionIconType.Circle} iconColor={iconColor} onClick={handleClick} + glowDuration={10} + glowStartDelay={0} + noScale={true} isSelected={isSelected} theme={theme} /> diff --git a/ts/components/session/icon/SessionIcon.tsx b/ts/components/session/icon/SessionIcon.tsx index 1818cf674..8ecaadf9d 100644 --- a/ts/components/session/icon/SessionIcon.tsx +++ b/ts/components/session/icon/SessionIcon.tsx @@ -12,6 +12,7 @@ export type SessionIconProps = { glowDuration?: number; borderRadius?: number; glowStartDelay?: number; + noScale?: boolean; theme?: DefaultTheme; }; @@ -46,6 +47,7 @@ type StyledSvgProps = { borderRadius?: number; glowDuration?: number; glowStartDelay?: number; + noScale?: boolean; iconColor?: string; }; @@ -91,16 +93,22 @@ const animation = (props: { glowDuration?: number; glowStartDelay?: number; iconColor?: string; + noScale?: boolean; }) => { if (props.rotateDuration) { return css` ${rotate} ${props.rotateDuration}s infinite linear; `; - } else if ( - props.glowDuration !== undefined && - props.glowStartDelay !== undefined && - props.iconColor - ) { + } + if (props.noScale) { + return css``; + } + + if (props.glowDuration === 10) { + console.warn('scake', props); + } + + if (props.glowDuration !== undefined && props.glowStartDelay !== undefined && props.iconColor) { return css` ${glow( props.iconColor, @@ -108,9 +116,9 @@ const animation = (props: { props.glowStartDelay )} ${props.glowDuration}s ease infinite; `; - } else { - return; } + + return; }; //tslint:disable no-unnecessary-callback-wrapper @@ -119,6 +127,7 @@ const Svg = styled.svg` transform: ${props => `rotate(${props.iconRotation}deg)`}; animation: ${props => animation(props)}; border-radius: ${props => props.borderRadius}; + filter: ${props => (props.noScale ? `drop-shadow(0px 0px 4px ${props.iconColor})` : '')}; `; //tslint:enable no-unnecessary-callback-wrapper @@ -132,6 +141,7 @@ const SessionSvg = (props: { rotateDuration?: number; glowDuration?: number; glowStartDelay?: number; + noScale?: boolean; borderRadius?: number; theme: DefaultTheme; }) => { @@ -146,6 +156,7 @@ const SessionSvg = (props: { glowDuration: props.glowDuration, glowStartDelay: props.glowStartDelay, iconColor: props.iconColor, + noScale: props.noScale, }; return ( @@ -166,6 +177,7 @@ export const SessionIcon = (props: SessionIconProps) => { glowDuration, borderRadius, glowStartDelay, + noScale, } = props; let { iconSize, iconRotation } = props; iconSize = iconSize || SessionIconSize.Medium; @@ -189,6 +201,7 @@ export const SessionIcon = (props: SessionIconProps) => { rotateDuration={rotateDuration} glowDuration={glowDuration} glowStartDelay={glowStartDelay} + noScale={noScale} borderRadius={borderRadius} iconRotation={iconRotation} iconColor={iconColor} diff --git a/ts/components/session/icon/SessionIconButton.tsx b/ts/components/session/icon/SessionIconButton.tsx index fe4c410e8..495cdce2e 100644 --- a/ts/components/session/icon/SessionIconButton.tsx +++ b/ts/components/session/icon/SessionIconButton.tsx @@ -20,6 +20,9 @@ export const SessionIconButton = (props: SProps) => { isSelected, notificationCount, theme, + glowDuration, + glowStartDelay, + noScale, } = props; const clickHandler = (e: any) => { if (props.onClick) { @@ -42,6 +45,9 @@ export const SessionIconButton = (props: SProps) => { iconColor={iconColor} iconRotation={iconRotation} theme={themeToUSe} + glowDuration={glowDuration} + glowStartDelay={glowStartDelay} + noScale={noScale} /> {Boolean(notificationCount) && } diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index f73c99acf..a4e81ed25 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -39,7 +39,7 @@ export interface SnodeResponse { export const NEXT_NODE_NOT_FOUND_PREFIX = 'Next node not found: '; export const CLOCK_OUT_OF_SYNC_MESSAGE_ERROR = - 'You clock is out of sync with the network. Check your clock'; + 'Your clock is out of sync with the network. Check your clock.'; // Returns the actual ciphertext, symmetric key that will be used // for decryption, and an ephemeral_key to send to the next hop diff --git a/ts/test/session/unit/onion/OnionErrors_test.ts b/ts/test/session/unit/onion/OnionErrors_test.ts index ef52c2909..44a9a538c 100644 --- a/ts/test/session/unit/onion/OnionErrors_test.ts +++ b/ts/test/session/unit/onion/OnionErrors_test.ts @@ -216,7 +216,7 @@ describe('OnionPathsErrors', () => { throw new Error('Error expected'); } catch (e) { expect(e.message).to.equal( - 'You clock is out of sync with the network. Check your clock.' + 'Your clock is out of sync with the network. Check your clock.' ); // this makes sure that this call would not be retried expect(e.name).to.equal('AbortError'); @@ -237,7 +237,7 @@ describe('OnionPathsErrors', () => { throw new Error('Error expected'); } catch (e) { expect(e.message).to.equal( - 'You clock is out of sync with the network. Check your clock.' + 'Your clock is out of sync with the network. Check your clock.' ); // this makes sure that this call would not be retried expect(e.name).to.equal('AbortError');