diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 442a633ef..cbfa1aab0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -426,5 +426,12 @@ "dialogClearAllDataDeletionFailedMultiple": "Data not deleted by those Service Nodes: $snodes$", "dialogClearAllDataDeletionQuestion": "Would you like to clear only this device, or delete your entire account?", "deviceOnly": "Device Only", - "entireAccount": "Entire Account" + "entireAccount": "Entire Account", + "recoveryPhraseSecureMessage": "Secure your account by saving your recovery phrase", + "recoveryPhraseSecureTitle": "You're almost finished!", + "recoveryPhraseSecureButtonText": "Secure your account", + "recoveryPhraseRevealMessage": "Reveal your recovery phrase then store it safely to secure it", + "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", + "recoveryPhraseInfoMessage": "Meet your recovery phrase Your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.", + "recoveryPhraseCompleteTitle": "Account secured!" } diff --git a/ts/components/dialog/DeleteAccountModal.tsx b/ts/components/dialog/DeleteAccountModal.tsx index b0e948019..40c4226f5 100644 --- a/ts/components/dialog/DeleteAccountModal.tsx +++ b/ts/components/dialog/DeleteAccountModal.tsx @@ -11,6 +11,7 @@ import { SessionWrapperModal } from '../session/SessionWrapperModal'; const deleteDbLocally = async () => { window?.log?.info('configuration message sent successfully. Deleting everything'); + window.persistStore?.purge(); await window.Signal.Logs.deleteAll(); await window.Signal.Data.removeAll(); await window.Signal.Data.close(); diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index b72d95285..9d32acdf6 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -1,8 +1,13 @@ -import React from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; -import { DefaultTheme, useTheme } from 'styled-components'; -import { SessionButton } from './SessionButton'; +import styled, { DefaultTheme, useTheme } from 'styled-components'; +import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; +import { Constants } from '../../session'; +import { UserUtils } from '../../session/utils'; +import { useDispatch, useSelector } from 'react-redux'; +import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; +import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; const Tab = ({ isSelected, @@ -17,8 +22,8 @@ const Tab = ({ }) => { const handleClick = onSelect ? () => { - onSelect(type); - } + onSelect(type); + } : undefined; return ( @@ -40,22 +45,232 @@ type Props = { export const LeftPaneSectionHeader = (props: Props) => { const { label, buttonIcon, buttonClicked } = props; - const theme = useTheme(); + const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); return ( -
- {label && } - {buttonIcon && ( - - - - )} -
+ +
+ {label && } + {buttonIcon && ( + + + + )} +
+ {showRecoveryPhrasePrompt && } +
); }; + +export const LeftPaneBanner = (Props: any) => { + + const [completion, setCompletion] = useState(80); + const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseSecureMessage')); + const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseSecureButtonText')); + const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); + const [isCompleted, setIsCompleted] = useState(false); + const [bannerTitle, setBannerTitle] = useState(window.i18n("recoveryPhraseSecureTitle")); + const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); + const secondsBeforeRemoval = 2 * 1000; + const completionText = `${completion}%`; + + const handleShowRecoveryClick = () => { + setRecoveryPhraseHidden(false); + setBodyText(window.i18n('recoveryPhraseInfoMessage')); + setButtonText(window.i18n('copy')); + } + + const handleSecureClick = () => { + if (completion === 80) { + setCompletion(90); + setBodyText(window.i18n('recoveryPhraseRevealMessage')); + setButtonText(window.i18n('recoveryPhraseRevealButtonText')); + } + } + + const BannerInner = (props: any) => { + const dispatch = useDispatch(); + + const handleCopyPhraseClick = async () => { + await navigator.clipboard.writeText(recoveryPhrase); + setCompletion(100) + setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); + setBodyText(''); + setRecoveryPhraseHidden(true); + setIsCompleted(true); + + // remove banner after a small delay + setTimeout(() => { + dispatch(disableRecoveryPhrasePrompt()); + }, secondsBeforeRemoval); + } + + let onClick = + completion === 80 ? handleSecureClick : + completion === 90 ? + recoveryPhraseHidden ? + handleShowRecoveryClick : + handleCopyPhraseClick + : null; + + // TODO: This can be refactored down. all returns have p tag + button, final has conditional phrase element. + return ( + +

+ {bodyText} +

+ {!recoveryPhraseHidden && + + {recoveryPhrase} + + } + {!isCompleted && + + } +
+ ) + } + + const useColumn = completion === 90 && handleShowRecoveryClick; + const theme = useTheme(); + + return ( + + + + + + {bannerTitle} {completionText} + + + + + + ) +} + +interface StyledProgressBarContainerProps { + theme: DefaultTheme; +} +const StyledProgressBarContainer = styled.div` + width: 100%; + height: 5px; + flex-direction: row; + background: ${(p: StyledProgressBarContainerProps) => p.theme.colors.sessionBorderColor}; +`; + +interface StyledProgressBarProps { + width: string; + color?: string; +} + +const StyledProgressBarInner = styled.div` + background: ${(p: StyledProgressBarProps) => p.color}; + width: ${(p: StyledProgressBarProps) => p.width}; + transition: width 0.5s ease-in; + height: 100%; +`; + +interface StyledBannerTitle { + theme: DefaultTheme; +} +export const StyledBannerTitle = styled.div` + line-height: 1.3; + font-size: ${(p: StyledBannerTitle) => p.theme.common.fonts.md}; + font-weight: bold; + margin: + ${Constants.UI.SPACING.marginSm} + ${Constants.UI.SPACING.marginSm} + 0 + ${Constants.UI.SPACING.marginSm}; + + span { + color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent}; + } +`; + +interface StyledLeftPaneBannerProps { + isCompleted?: boolean; + border: string; + theme: DefaultTheme; +} +export const StyledLeftPaneBanner = styled.div` + background: ${(p: StyledLeftPaneBannerProps) => p.theme.colors.recoveryPhraseBannerBackground}; + display: flex; + flex-direction: column; + border-bottom: ${(p: StyledLeftPaneBannerProps) => p.border}; + opacity: 1; + transition: opacity 2s; + ${(p: StyledLeftPaneBannerProps) => p.isCompleted === true ? + ` + opacity: 0; + ` + : + null + } +`; + +const StyledBannerInner = styled.div` + p { + margin: 0; + } + + .left-pane-banner___phrase { + margin-top: ${Constants.UI.SPACING.marginMd}; + } + + .session-button { + margin-top: ${Constants.UI.SPACING.marginSm}; + } +`; + +interface StyledRecoveryPhraseProps { + theme: DefaultTheme; +} +const StyledRecoveryPhrase = styled.p` + margin: ${Constants.UI.SPACING.marginXs}; + border-radius: 5px; + padding: 5px; + border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast}; +`; + + +// 90% + +// tap and hold the redacted words to reveal your recovery phrase then store it safely to secure + +// meet your recovery phrase +// your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone. + +interface StyledBannerContainerProps { + flexDirection?: string; +} +export const StyledBannerContainer = styled.div` + display: flex; + flex-direction: ${(p: StyledBannerContainerProps) => p.flexDirection}; + justify-content: space-between; + padding: ${Constants.UI.SPACING.marginSm} +`; + +export const StyledLeftPaneHeaderContainer = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/ts/components/session/LeftPaneSettingSection.tsx b/ts/components/session/LeftPaneSettingSection.tsx index fb71e90c2..4f6617089 100644 --- a/ts/components/session/LeftPaneSettingSection.tsx +++ b/ts/components/session/LeftPaneSettingSection.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import classNames from 'classnames'; import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; @@ -13,6 +13,12 @@ import { getFocusedSettingsSection } from '../../state/selectors/section'; import { getTheme } from '../../state/selectors/theme'; import { recoveryPhraseModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog'; +type Props = { + settingsCategory: SessionSettingCategory; + showSettingsSection: (category: SessionSettingCategory) => void; + theme: DefaultTheme; +}; + const getCategories = () => { return [ { diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 9357b30bc..defd3845b 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -52,6 +52,7 @@ export class SessionInboxView extends React.Component { } const persistor = persistStore(this.store); + window.persistStore = persistor; return ( diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 58d8a2059..94d3a9beb 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -3,12 +3,11 @@ import classNames from 'classnames'; import moment from 'moment'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; -import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { Constants } from '../../../session'; import { ToastUtils } from '../../../session/utils'; import autoBind from 'auto-bind'; import MicRecorder from 'mic-recorder-to-mp3'; -import styled, { useTheme } from 'styled-components'; +import styled from 'styled-components'; interface Props { onExitVoiceNoteView: any; @@ -164,7 +163,7 @@ class SessionRecordingInner extends React.Component { )} {hasRecording && ( @@ -366,7 +365,6 @@ class SessionRecordingInner extends React.Component { const audioURL = window.URL.createObjectURL(this.audioBlobMp3); this.audioElement = new Audio(audioURL); - // ww adding record duration this.setState({ recordDuration: this.audioElement.duration, }); diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx index fc18eff79..c95a62543 100644 --- a/ts/components/session/icon/Icons.tsx +++ b/ts/components/session/icon/Icons.tsx @@ -13,8 +13,6 @@ export enum SessionIconType { CircleElipses = 'circleElipses', Contacts = 'contacts', Delete = 'delete', - Delete2 = 'delete2', - Delete3 = 'delete3', Ellipses = 'ellipses', Emoji = 'emoji', Error = 'error', @@ -130,23 +128,11 @@ export const icons = { ratio: 1, }, [SessionIconType.Delete]: { - path: - 'M1.63 97.99l36.55-36.55L1.63 24.89c-2.17-2.17-2.17-5.73 0-7.9L16.99 1.63c2.17-2.17 5.73-2.17 7.9 0l36.55 36.55L97.99 1.63c2.17-2.17 5.73-2.17 7.9 0l15.36 15.36c2.17 2.17 2.17 5.73 0 7.9L84.7 61.44l36.55 36.55c2.17 2.17 2.17 5.73 0 7.9l-15.36 15.36c-2.17 2.17-5.73 2.17-7.9 0L61.44 84.7l-36.55 36.55c-2.17 2.17-5.73 2.17-7.9 0L1.63 105.89c-2.17-2.17-2.17-5.73 0-7.9z', - viewBox: '0 0 122.88 122.88', - ratio: 1, - }, - [SessionIconType.Delete2]: { path: 'M11.17 37.16h83.48a8.4 8.4 0 012 .16 5.93 5.93 0 012.88 1.56 5.43 5.43 0 011.64 3.34 7.65 7.65 0 01-.06 1.44L94 117.31V117.72a7.06 7.06 0 01-.2.9v.06a5.89 5.89 0 01-5.47 4.07H17.32a6.17 6.17 0 01-1.25-.19 6.17 6.17 0 01-1.16-.48 6.18 6.18 0 01-3.08-4.88l-7-73.49a7.69 7.69 0 01-.06-1.66 5.37 5.37 0 011.63-3.29 6 6 0 013-1.58 8.94 8.94 0 011.79-.13zM5.65 8.8h31.47V6a2.44 2.44 0 010-.27 6 6 0 011.76-4A6 6 0 0143.09 0h19.67a6 6 0 015.7 6v2.8h32.39a4.7 4.7 0 014.31 4.43v10.36a2.59 2.59 0 01-2.59 2.59H2.59A2.59 2.59 0 010 23.62V13.53a1.56 1.56 0 010-.31 4.72 4.72 0 013.88-4.34 10.4 10.4 0 011.77-.08zm42.1 52.7a4.77 4.77 0 019.49 0v37a4.77 4.77 0 01-9.49 0v-37zm23.73-.2a4.58 4.58 0 015-4.06 4.47 4.47 0 014.51 4.46l-2 37a4.57 4.57 0 01-5 4.06 4.47 4.47 0 01-4.51-4.46l2-37zM25 61.7a4.46 4.46 0 014.5-4.46 4.58 4.58 0 015 4.06l2 37a4.47 4.47 0 01-4.51 4.46 4.57 4.57 0 01-5-4.06l-2-37z', viewBox: '0 0 105.16 122.88', ratio: 1, }, - [SessionIconType.Delete3]: { - path: - 'M4.873 9.058h33.35V6.187c0-.095.002-.186.014-.279.075-1.592.762-3.037 1.816-4.086l-.007-.007C41.15.711 42.683.025 44.371.009l.023.002V0H64.325c.106 0 .207.009.309.022 1.583.084 3.019.76 4.064 1.81 1.102 1.104 1.786 2.635 1.803 4.315l-.003.021h.014V9.057h32.926c.138 0 .268.014.401.034 1.182.106 2.254.625 3.034 1.41l.004.007.005-.007c.851.857 1.386 2.048 1.401 3.368l-.002.032h.014v10.861c0 1.472-1.195 2.665-2.667 2.665H2.667C1.195 27.426 0 26.233 0 24.762V13.919c0-.106.004-.211.018-.315v-.021c.089-1.207.624-2.304 1.422-3.098l-.007-.002c.862-.861 2.057-1.396 3.377-1.414l.032.002v-.013h.031zM77.79 49.097h-5.945v56.093h5.945V49.097zm-19.33 0h-5.948v56.093h5.948V49.097zm-19.33 0h-5.946v56.093h5.946V49.097zM10.837 31.569h87.385l.279.018.127.007.134.011h.009l.163.023c1.363.163 2.638.789 3.572 1.708 1.04 1.025 1.705 2.415 1.705 3.964 0 .098-.009.193-.019.286l-.002.068-.014.154-7.393 79.335-.007.043h.007l-.016.139-.051.283-.002.005-.002.018c-.055.331-.12.646-.209.928l-.007.022-.002.005-.009.018-.023.062-.004.021c-.118.354-.264.698-.432 1.009-1.009 1.88-2.879 3.187-5.204 3.187H18.13l-.247-.014v.003l-.011-.003-.032-.004c-.46-.023-.889-.091-1.288-.202-.415-.116-.818-.286-1.197-.495l-.009-.002-.002.002c-1.785-.977-2.975-2.882-3.17-5.022L4.88 37.79l-.011-.125-.011-.247-.004-.116h-.005c0-1.553.664-2.946 1.707-3.971.976-.955 2.32-1.599 3.756-1.726l.122-.004v-.007l.3-.013.104.002v-.014h-.001zm87.386 5.334H10.837v-.007l-.116.004c-.163.022-.322.106-.438.222-.063.063-.104.132-.104.179h-.007l.007.118 7.282 79.244h-.002l.002.012c.032.376.202.691.447.825l-.002.004.084.032.063.012H90.825c.207 0 .399-.157.518-.377l.084-.197.054-.216.014-.138h.005l7.384-79.21-.003-.11c0-.045-.041-.111-.103-.172-.12-.118-.286-.202-.451-.227l-.104.002zm.111-.002h-.016.016zm.549.512v-.004.004zm5.297.377l-.002.018.002-.018zM40.887 14.389H5.332v7.706h97.63v-7.706H67.844c-1.472 0-2.664-1.192-2.664-2.664V6.168h.007c-.007-.22-.106-.433-.259-.585-.137-.141-.324-.229-.521-.252H44.394v-.006c-.213.007-.422.104-.576.259l-.004-.004-.007.004c-.131.134-.231.313-.259.501l.007.102v5.537c-.001 1.472-1.196 2.665-2.668 2.665z', - viewBox: '0 0 108.294 122.88', - ratio: 1, - }, [SessionIconType.DoubleCheckCircleFilled]: { path: 'M7.91731278,0.313257194 C6.15053376,1.58392424 5,3.65760134 5,6 C5,6.343797 5.0247846,6.68180525 5.07266453,7.01233547 L5,7.085 L3.205,5.295 L2.5,6 L5,8.5 L5.33970233,8.16029767 C5.80439817,9.59399486 6.71914823,10.8250231 7.91731278,11.6867428 C7.31518343,11.8898758 6.67037399,12 6,12 C2.688,12 0,9.312 0,6 C0,2.688 2.688,0 6,0 C6.67037399,0 7.31518343,0.110124239 7.91731278,0.313257194 Z M12,0 C15.312,0 18,2.688 18,6 C18,9.312 15.312,12 12,12 C8.688,12 6,9.312 6,6 C6,2.688 8.688,0 12,0 Z M11,8.5 L15.5,4 L14.795,3.29 L11,7.085 L9.205,5.295 L8.5,6 L11,8.5 Z', diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx index 6a4c43f93..575a8ce0a 100644 --- a/ts/state/ducks/SessionTheme.tsx +++ b/ts/state/ducks/SessionTheme.tsx @@ -3,7 +3,6 @@ import React from 'react'; // import 'reset-css/reset.css'; import { DefaultTheme, ThemeProvider } from 'styled-components'; -import { pushToastWarning } from '../../session/utils/Toast'; const white = '#ffffff'; const black = '#000000'; @@ -11,8 +10,10 @@ const warning = '#e7b100'; const destructive = '#ff453a'; const accentLightTheme = '#00e97b'; const accentDarkTheme = '#00f782'; -const borderLightTheme = '#f1f1f1'; -const borderDarkTheme = '#ffffff0F'; +const borderLightThemeColor = '#f1f1f1'; +const borderDarkThemeColor = '#ffffff0F'; +const borderHighContrastLightTheme = '#afafaf'; +const borderHighContrastDarkTheme = '#484848'; const borderAvatarColor = '#00000059'; const common = { @@ -55,6 +56,7 @@ export const lightTheme: DefaultTheme = { textColorSubtleNoOpacity: '#52514f', textColorOpposite: white, textHighlight: `${black}33`, + textAccent: '#00c769', // inbox inboxBackground: white, // buttons @@ -75,9 +77,12 @@ export const lightTheme: DefaultTheme = { conversationItemHasUnread: '#fcfcfc', conversationItemSelected: '#f0f0f0', clickableHovered: '#dfdfdf', - sessionBorder: `1px solid ${borderLightTheme}`, + sessionBorder: `1px solid ${borderLightThemeColor}`, + sessionBorderColor: borderLightThemeColor, + sessionBorderHighContrast: `1px solid ${borderHighContrastLightTheme}`, sessionUnreadBorder: `4px solid ${accentLightTheme}`, leftpaneOverlayBackground: white, + recoveryPhraseBannerBackground: white, // scrollbars scrollBarTrack: '#fcfcfc', scrollBarThumb: '#474646', @@ -111,6 +116,7 @@ export const darkTheme = { textColorSubtleNoOpacity: '#7f7d7d', textColorOpposite: black, textHighlight: `${accentDarkTheme}99`, + textAccent: accentDarkTheme, // inbox inboxBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)', // buttons @@ -131,9 +137,12 @@ export const darkTheme = { conversationItemHasUnread: '#2c2c2c', conversationItemSelected: '#404040', clickableHovered: '#414347', - sessionBorder: `1px solid ${borderDarkTheme}`, + sessionBorder: `1px solid ${borderDarkThemeColor}`, + sessionBorderColor: borderDarkThemeColor, + sessionBorderHighContrast: `1px solid ${borderHighContrastDarkTheme}`, sessionUnreadBorder: `4px solid ${accentDarkTheme}`, leftpaneOverlayBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)', + recoveryPhraseBannerBackground: '#1f1f1f', // scrollbars scrollBarTrack: '#1b1b1b', scrollBarThumb: '#474646', diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx index 1c704e2b4..20316fcdf 100644 --- a/ts/state/ducks/userConfig.tsx +++ b/ts/state/ducks/userConfig.tsx @@ -6,10 +6,12 @@ import { createSlice } from '@reduxjs/toolkit'; export interface UserConfigState { audioAutoplay: boolean; + showRecoveryPhrasePrompt: boolean; } export const initialUserConfigState = { audioAutoplay: false, + showRecoveryPhrasePrompt: true, }; const userConfigSlice = createSlice({ @@ -19,9 +21,14 @@ const userConfigSlice = createSlice({ toggleAudioAutoplay: state => { state.audioAutoplay = !state.audioAutoplay; }, + disableRecoveryPhrasePrompt: state => { + console.log('setting recovery phrase state'); + state.showRecoveryPhrasePrompt = false + }, + }, }); const { actions, reducer } = userConfigSlice; -export const { toggleAudioAutoplay } = actions; +export const { toggleAudioAutoplay, disableRecoveryPhrasePrompt } = actions; export const userConfigReducer = reducer; diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts index 084f8d9ac..1e3de4c7c 100644 --- a/ts/state/selectors/userConfig.ts +++ b/ts/state/selectors/userConfig.ts @@ -8,3 +8,9 @@ export const getAudioAutoplay = createSelector( getUserConfig, (state: UserConfigState): boolean => state.audioAutoplay ); + + +export const getShowRecoveryPhrasePrompt = createSelector( + getUserConfig, + (state: UserConfigState): boolean => state.showRecoveryPhrasePrompt +); diff --git a/ts/styled.d.ts b/ts/styled.d.ts index 905375e34..953f901d3 100644 --- a/ts/styled.d.ts +++ b/ts/styled.d.ts @@ -39,6 +39,7 @@ declare module 'styled-components' { textColorSubtleNoOpacity: string; textColorOpposite: string; textHighlight: string; + textAccent: string; // inbox inboxBackground: string; // buttons @@ -60,8 +61,11 @@ declare module 'styled-components' { conversationItemSelected: string; clickableHovered: string; sessionBorder: string; + sessionBorderColor: string; + sessionBorderHighContrast: string; sessionUnreadBorder: string; leftpaneOverlayBackground: string; + recoveryPhraseBannerBackground: string; // scrollbars scrollBarTrack: string; scrollBarThumb: string; diff --git a/ts/window.d.ts b/ts/window.d.ts index c4a8c17ea..2ba243d01 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -52,6 +52,7 @@ declare global { }; lokiSnodeAPI: LokiSnodeAPI; onLogin: any; + persistStore?: Persistor; resetDatabase: any; restart: any; getSeedNodeList: () => Array | undefined;