From 15aa6b5ef969922abee7227f15d19b687d4f92f5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann <audric@loki.network> Date: Mon, 28 Jun 2021 13:31:21 +1000 Subject: [PATCH 1/7] add loading for leaving opengroup dialog --- ts/components/session/SessionConfirm.tsx | 2 +- ts/components/session/SessionToggle.tsx | 105 +++++------- ts/components/session/menu/Menu.tsx | 7 +- .../settings/SessionSettingListItem.tsx | 161 +++++++----------- .../session/settings/SessionSettings.tsx | 2 - ts/interactions/conversationInteractions.ts | 10 +- ts/interactions/messageInteractions.ts | 4 +- 7 files changed, 120 insertions(+), 171 deletions(-) diff --git a/ts/components/session/SessionConfirm.tsx b/ts/components/session/SessionConfirm.tsx index 27b40bb93..190c11632 100644 --- a/ts/components/session/SessionConfirm.tsx +++ b/ts/components/session/SessionConfirm.tsx @@ -17,7 +17,7 @@ export interface SessionConfirmDialogProps { title?: string; onOk?: any; onClose?: any; - onClickOk?: () => any; + onClickOk?: () => Promise<void> | void; onClickClose?: () => any; okText?: string; cancelText?: string; diff --git a/ts/components/session/SessionToggle.tsx b/ts/components/session/SessionToggle.tsx index eb7ed2de4..378b18a46 100644 --- a/ts/components/session/SessionToggle.tsx +++ b/ts/components/session/SessionToggle.tsx @@ -1,84 +1,63 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import { updateConfirmModal } from '../../state/ducks/modalDialog'; +import { useDispatch } from 'react-redux'; -interface Props { +type Props = { active: boolean; - onClick: any; + onClick: () => void; confirmationDialogParams?: any | undefined; +}; - updateConfirmModal?: any; -} +export const SessionToggle = (props: Props) => { + const [active, setActive] = useState(false); -interface State { - active: boolean; -} - -export class SessionToggle extends React.PureComponent<Props, State> { - public static defaultProps = { - onClick: () => null, - }; - - constructor(props: any) { - super(props); - this.clickHandler = this.clickHandler.bind(this); - - const { active } = this.props; + const dispatch = useDispatch(); - this.state = { - active: active, - }; - } - - public render() { - return ( - <div - className={classNames('session-toggle', this.state.active ? 'active' : '')} - role="button" - onClick={this.clickHandler} - > - <div className="knob" /> - </div> - ); - } + useEffect(() => { + setActive(props.active); + }, []); - private clickHandler(event: any) { + const clickHandler = (event: any) => { const stateManager = (e: any) => { - this.setState({ - active: !this.state.active, - }); - - if (this.props.onClick) { - e.stopPropagation(); - this.props.onClick(); - } + setActive(!active); + e.stopPropagation(); + props.onClick(); }; - if ( - this.props.confirmationDialogParams && - this.props.updateConfirmModal && - this.props.confirmationDialogParams.shouldShowConfirm() - ) { + if (props.confirmationDialogParams && props.confirmationDialogParams.shouldShowConfirm()) { // If item needs a confirmation dialog to turn ON, render it const closeConfirmModal = () => { - this.props.updateConfirmModal(null); + dispatch(updateConfirmModal(null)); }; - this.props.updateConfirmModal({ - onClickOk: () => { - stateManager(event); - closeConfirmModal(); - }, - onClickClose: () => { - this.props.updateConfirmModal(null); - }, - ...this.props.confirmationDialogParams, - updateConfirmModal, - }); + dispatch( + updateConfirmModal({ + onClickOk: () => { + stateManager(event); + closeConfirmModal(); + }, + onClickClose: () => { + updateConfirmModal(null); + }, + ...props.confirmationDialogParams, + updateConfirmModal, + }) + ); return; } stateManager(event); - } -} + }; + + return ( + <div + className={classNames('session-toggle', active ? 'active' : '')} + role="button" + onClick={clickHandler} + > + <div className="knob" /> + </div> + ); +}; diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 39b741a86..e7c0bc1b5 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -25,6 +25,7 @@ import { showUpdateGroupNameByConvoId, unblockConvoById, } from '../../../interactions/conversationInteractions'; +import { SessionButtonColor } from '../SessionButton'; function showTimerOptions( isPublic: boolean, @@ -162,9 +163,9 @@ export function getDeleteContactMenuItem( ? window.i18n('leaveGroupConfirmation') : window.i18n('deleteContactConfirmation'), onClickClose, - onClickOk: () => { - void getConversationController().deleteContact(conversationId); - onClickClose(); + okTheme: SessionButtonColor.Danger, + onClickOk: async () => { + await getConversationController().deleteContact(conversationId); }, }) ); diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index f205fd487..66c18d4fa 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; import Slider from 'rc-slider'; @@ -9,7 +9,7 @@ import { SessionSettingType } from './SessionSettings'; import { SessionRadioGroup } from '../SessionRadioGroup'; import { SessionConfirmDialogProps } from '../SessionConfirm'; -interface Props { +type Props = { title?: string; description?: string; type: SessionSettingType | undefined; @@ -19,112 +19,79 @@ interface Props { onSliderChange?: any; content: any; confirmationDialogParams?: SessionConfirmDialogProps; +}; - // for updating modal in redux - updateConfirmModal?: any; -} - -interface State { - sliderValue: number | null; -} +export const SessionSettingListItem = (props: Props) => { + const handleSlider = (value: any) => { + if (props.onSliderChange) { + props.onSliderChange(value); + } -export class SessionSettingListItem extends React.Component<Props, State> { - public static defaultProps = { - inline: true, + setSliderValue(value); }; - public constructor(props: Props) { - super(props); - this.state = { - sliderValue: null, - }; - - this.handleClick = this.handleClick.bind(this); - } - - public render(): JSX.Element { - const { title, description, type, value, content } = this.props; - const inline = - !!type && ![SessionSettingType.Options, SessionSettingType.Slider].includes(type); + const [sliderValue, setSliderValue] = useState(null); - const currentSliderValue = - type === SessionSettingType.Slider && (this.state.sliderValue || value); + const { title, description, type, value, content } = props; + const inline = !!type && ![SessionSettingType.Options, SessionSettingType.Slider].includes(type); - return ( - <div className={classNames('session-settings-item', inline && 'inline')}> - <div className="session-settings-item__info"> - <div className="session-settings-item__title">{title}</div> + const currentSliderValue = type === SessionSettingType.Slider && (sliderValue || value); - {description && <div className="session-settings-item__description">{description}</div>} - </div> + return ( + <div className={classNames('session-settings-item', inline && 'inline')}> + <div className="session-settings-item__info"> + <div className="session-settings-item__title">{title}</div> - <div className="session-settings-item__content"> - {type === SessionSettingType.Toggle && ( - <div className="session-settings-item__selection"> - <SessionToggle - active={Boolean(value)} - onClick={this.handleClick} - confirmationDialogParams={this.props.confirmationDialogParams} - updateConfirmModal={this.props.updateConfirmModal} - /> - </div> - )} + {description && <div className="session-settings-item__description">{description}</div>} + </div> - {type === SessionSettingType.Button && ( - <SessionButton - text={content.buttonText} - buttonColor={content.buttonColor} - onClick={this.handleClick} + <div className="session-settings-item__content"> + {type === SessionSettingType.Toggle && ( + <div className="session-settings-item__selection"> + <SessionToggle + active={Boolean(value)} + onClick={() => props.onClick?.()} + confirmationDialogParams={props.confirmationDialogParams} /> - )} - - {type === SessionSettingType.Options && ( - <SessionRadioGroup - initialItem={content.options.initalItem} - group={content.options.group} - items={content.options.items} - onClick={(selectedRadioValue: string) => { - this.props.onClick(selectedRadioValue); - }} + </div> + )} + + {type === SessionSettingType.Button && ( + <SessionButton + text={content.buttonText} + buttonColor={content.buttonColor} + onClick={() => props.onClick?.()} + /> + )} + + {type === SessionSettingType.Options && ( + <SessionRadioGroup + initialItem={content.options.initalItem} + group={content.options.group} + items={content.options.items} + onClick={(selectedRadioValue: string) => { + props.onClick(selectedRadioValue); + }} + /> + )} + + {type === SessionSettingType.Slider && ( + <div className="slider-wrapper"> + <Slider + dots={true} + step={content.step} + min={content.min} + max={content.max} + defaultValue={currentSliderValue} + onAfterChange={handleSlider} /> - )} - {type === SessionSettingType.Slider && ( - <div className="slider-wrapper"> - <Slider - dots={true} - step={content.step} - min={content.min} - max={content.max} - defaultValue={currentSliderValue} - onAfterChange={sliderValue => { - this.handleSlider(sliderValue); - }} - /> - - <div className="slider-info"> - <p>{content.info(currentSliderValue)}</p> - </div> + <div className="slider-info"> + <p>{content.info(currentSliderValue)}</p> </div> - )} - </div> + </div> + )} </div> - ); - } - - private handleClick() { - if (this.props.onClick) { - this.props.onClick(); - } - } - - private handleSlider(value: any) { - if (this.props.onSliderChange) { - this.props.onSliderChange(value); - } - - this.setState({ - sliderValue: value, - }); - } -} + </div> + ); +}; diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index d03453754..abcff8bb7 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -40,7 +40,6 @@ export interface SettingsViewProps { // pass the conversation as props, so our render is called everytime they change. // we have to do this to make the list refresh on unblock() conversations?: ConversationLookupType; - updateConfirmModal?: any; } interface State { @@ -156,7 +155,6 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> { onSliderChange={sliderFn} content={content} confirmationDialogParams={setting.confirmationDialogParams} - updateConfirmModal={this.props.updateConfirmModal} /> )} </div> diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index ce9b35e1f..2aa01feef 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -36,6 +36,7 @@ import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsMana import { IMAGE_JPEG } from '../types/MIME'; import { FSv2 } from '../fileserver'; import { fromBase64ToArray, toHex } from '../session/utils/String'; +import { SessionButtonColor } from '../components/session/SessionButton'; export const getCompleteUrlForV2ConvoId = async (convoId: string) => { if (convoId.match(openGroupV2ConversationIdRegex)) { @@ -219,8 +220,8 @@ export function showLeaveGroupByConvoId(conversationId: string) { updateConfirmModal({ title, message, - onClickOk: () => { - void conversation.leaveClosedGroup(); + onClickOk: async () => { + await conversation.leaveClosedGroup(); onClickClose(); }, onClickClose, @@ -302,8 +303,8 @@ export function deleteMessagesByConvoIdWithConfirmation(conversationId: string) window?.inboxStore?.dispatch(updateConfirmModal(null)); }; - const onClickOk = () => { - void deleteMessagesByConvoIdNoConfirmation(conversationId); + const onClickOk = async () => { + await deleteMessagesByConvoIdNoConfirmation(conversationId); onClickClose(); }; @@ -312,6 +313,7 @@ export function deleteMessagesByConvoIdWithConfirmation(conversationId: string) title: window.i18n('deleteMessages'), message: window.i18n('deleteConversationConfirmation'), onClickOk, + okTheme: SessionButtonColor.Danger, onClickClose, }) ); diff --git a/ts/interactions/messageInteractions.ts b/ts/interactions/messageInteractions.ts index 8199e427a..50b6d9268 100644 --- a/ts/interactions/messageInteractions.ts +++ b/ts/interactions/messageInteractions.ts @@ -173,7 +173,9 @@ const acceptOpenGroupInvitationV2 = (completeUrl: string, roomName?: string) => updateConfirmModal({ title: window.i18n('joinOpenGroupAfterInvitationConfirmationTitle', roomName), message: window.i18n('joinOpenGroupAfterInvitationConfirmationDesc', roomName), - onClickOk: () => joinOpenGroupV2WithUIEvents(completeUrl, true, false), + onClickOk: async () => { + await joinOpenGroupV2WithUIEvents(completeUrl, true, false); + }, onClickClose, }) From 5e55c2cfabfbc486e3c3f3102253b439600e117a Mon Sep 17 00:00:00 2001 From: Audric Ackermann <audric@loki.network> Date: Mon, 28 Jun 2021 15:09:14 +1000 Subject: [PATCH 2/7] fix lint --- ts/components/session/settings/SessionSettingListItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 66c18d4fa..fe04793e1 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -22,12 +22,12 @@ type Props = { }; export const SessionSettingListItem = (props: Props) => { - const handleSlider = (value: any) => { + const handleSlider = (valueToForward: any) => { if (props.onSliderChange) { - props.onSliderChange(value); + props.onSliderChange(valueToForward); } - setSliderValue(value); + setSliderValue(valueToForward); }; const [sliderValue, setSliderValue] = useState(null); From e85f69a144e5e0dc1296382380334f02c70b2b09 Mon Sep 17 00:00:00 2001 From: Audric Ackermann <audric@loki.network> Date: Tue, 29 Jun 2021 13:48:43 +1000 Subject: [PATCH 3/7] 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 ( <> - <SessionOffline /> {props.isExpired && <SessionExpiredWarning />} <LeftPaneMessageSection theme={theme} @@ -74,7 +72,6 @@ const InnerLeftPaneContactSection = () => { return ( <> - <SessionOffline /> <LeftPaneContactSection openConversationExternal={(id, messageId) => 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 <SessionSpinner loading={true} />; } @@ -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<ContainerProps>` - 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 ( - <OfflineContainer show={!isOnline}> - <OfflineTitle>{window.i18n('offline')}</OfflineTitle> - <OfflineMessage>{window.i18n('checkNetworkConnection')}</OfflineMessage> - </OfflineContainer> - ); -}; 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<string, number> = {}; 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<void> { - 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<Array<Snode>>; + isOnline: boolean; }; export const initialOnionPathState = { snodePaths: new Array<Array<Snode>>(), + isOnline: false, }; /** @@ -17,12 +19,15 @@ const onionSlice = createSlice({ initialState: initialOnionPathState, reducers: { updateOnionPaths(state: OnionState, action: PayloadAction<Array<Array<Snode>>>) { - return { snodePaths: action.payload }; + return { ...state, snodePaths: action.payload }; + }, + updateIsOnline(state: OnionState, action: PayloadAction<boolean>) { + 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<Snode>): 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 <audric@loki.network> Date: Tue, 29 Jun 2021 14:55:59 +1000 Subject: [PATCH 4/7] 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<MessageAttributes> { 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<DestinationContext> { @@ -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 <audric@loki.network> Date: Tue, 29 Jun 2021 15:20:21 +1000 Subject: [PATCH 5/7] 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<StyledSvgProps>` 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) && <SessionNotificationCount count={notificationCount} />} </div> 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'); From 52293d6787a051c705e0763da06469270fc91c3b Mon Sep 17 00:00:00 2001 From: Audric Ackermann <audric@loki.network> Date: Tue, 29 Jun 2021 16:31:57 +1000 Subject: [PATCH 6/7] fix trust do not trigger redownload of already dl medias --- package.json | 2 +- ts/components/conversation/message/ClickToTrustSender.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a99ffbff1..2af38bea6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.6.6", + "version": "1.6.7", "license": "GPL-3.0", "author": { "name": "Loki Project", diff --git a/ts/components/conversation/message/ClickToTrustSender.tsx b/ts/components/conversation/message/ClickToTrustSender.tsx index 43eb71736..257755185 100644 --- a/ts/components/conversation/message/ClickToTrustSender.tsx +++ b/ts/components/conversation/message/ClickToTrustSender.tsx @@ -58,6 +58,9 @@ export const ClickToTrustSender = (props: { messageId: string }) => { const downloadedAttachments = await Promise.all( msgAttachments.map(async (attachment: any, index: any) => { + if (attachment.path) { + return { ...attachment, pending: false }; + } return AttachmentDownloads.addJob(attachment, { messageId: message.id, type: 'attachment', From 9cb69cf8fdd8c553ee1ca32779206231a2209c2d Mon Sep 17 00:00:00 2001 From: audric <audric@loki.network> Date: Wed, 30 Jun 2021 15:02:22 +1000 Subject: [PATCH 7/7] do not start expire timer before seing message --- ts/receiver/queuedJob.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index e5fe07924..04c0c719f 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -318,7 +318,6 @@ async function handleRegularMessage( if (existingExpireTimer) { message.set({ expireTimer: existingExpireTimer }); - message.set({ expirationStartTimestamp: now }); } // Expire timer updates are now explicit.