diff --git a/.gitignore b/.gitignore index 27cf464dbe..46770cfbbc 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ stylesheets/dist/ *.LICENSE.txt ts/webworker/workers/node/**/*.node +.yarn/**/*.mjs +.yarn/**/*.cjs diff --git a/package.json b/package.json index 7c68497006..8e754b913b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.12.2", + "version": "1.12.3", "license": "GPL-3.0", "author": { "name": "Oxen Labs", @@ -216,11 +216,13 @@ "afterSign": "build/notarize.js", "afterPack": "build/afterPackHook.js", "artifactName": "${name}-${os}-${arch}-${version}.${ext}", - "extraResources": [{ - "from": "./build/launcher-script.sh", - "to": "./launcher-script.sh" - }, - "mmdb/GeoLite2-Country.mmdb"], + "extraResources": [ + { + "from": "./build/launcher-script.sh", + "to": "./launcher-script.sh" + }, + "mmdb/GeoLite2-Country.mmdb" + ], "mac": { "category": "public.app-category.social-networking", "icon": "build/icon-mac.icns", diff --git a/ts/components/DebugLogView.tsx b/ts/components/DebugLogView.tsx index 3caa50b485..5ffd340130 100644 --- a/ts/components/DebugLogView.tsx +++ b/ts/components/DebugLogView.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { switchThemeTo } from '../themes/switchTheme'; import { SessionTheme } from '../themes/SessionTheme'; -import { fetch } from '../util/logging'; +import { fetchNodeLog } from '../util/logging'; import { SessionButton, SessionButtonType } from './basic/SessionButton'; import { SessionIconButton } from './icon'; @@ -78,7 +78,7 @@ const DebugLogViewAndSave = () => { const commitHashInfo = window.getCommitHash() ? `Commit Hash: ${window.getCommitHash()}` : ''; // eslint-disable-next-line more/no-then - fetch() + fetchNodeLog() .then((text: any) => { const debugLogWithSystemInfo = `${operatingSystemInfo} ${commitHashInfo} ${text}`; setContent(debugLogWithSystemInfo); diff --git a/ts/components/SessionInboxView.tsx b/ts/components/SessionInboxView.tsx index f2d8c76165..b4ff71b007 100644 --- a/ts/components/SessionInboxView.tsx +++ b/ts/components/SessionInboxView.tsx @@ -91,7 +91,18 @@ function createSessionInboxStore() { function setupLeftPane(forceUpdateInboxComponent: () => void) { window.openConversationWithMessages = openConversationWithMessages; window.inboxStore = createSessionInboxStore(); - window.inboxStore.dispatch(updateAllOnStorageReady()); + + window.inboxStore.dispatch( + updateAllOnStorageReady({ + hasBlindedMsgRequestsEnabled: Storage.getBoolOrFalse( + SettingsKey.hasBlindedMsgRequestsEnabled + ), + someDeviceOutdatedSyncing: Storage.getBoolOrFalse(SettingsKey.someDeviceOutdatedSyncing), + settingsLinkPreview: Storage.getBoolOrFalse(SettingsKey.settingsLinkPreview), + hasFollowSystemThemeEnabled: Storage.getBoolOrFalse(SettingsKey.hasFollowSystemThemeEnabled), + hasShiftSendEnabled: Storage.getBoolOrFalse(SettingsKey.hasShiftSendEnabled), + }) + ); forceUpdateInboxComponent(); } diff --git a/ts/components/basic/SessionInput.tsx b/ts/components/basic/SessionInput.tsx index 0c33a22b0a..a34de03cc8 100644 --- a/ts/components/basic/SessionInput.tsx +++ b/ts/components/basic/SessionInput.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import classNames from 'classnames'; import { SessionIconButton } from '../icon'; -import { Noop } from '../../types/Util'; import { useHTMLDirection } from '../../util/i18n'; type Props = { @@ -46,7 +45,7 @@ const ErrorItem = (props: { error: string | undefined }) => { ); }; -const ShowHideButton = (props: { toggleForceShow: Noop }) => { +const ShowHideButton = (props: { toggleForceShow: () => void }) => { const htmlDirection = useHTMLDirection(); const position = htmlDirection === 'ltr' ? { right: '0px' } : { left: '0px' }; diff --git a/ts/components/calling/IncomingCallDialog.tsx b/ts/components/calling/IncomingCallDialog.tsx index ccaba5c465..a3ec501d58 100644 --- a/ts/components/calling/IncomingCallDialog.tsx +++ b/ts/components/calling/IncomingCallDialog.tsx @@ -3,13 +3,13 @@ import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { useConversationUsername } from '../../hooks/useParamSelector'; -import { ed25519Str } from '../../session/onions/onionPath'; import { CallManager } from '../../session/utils'; import { callTimeoutMs } from '../../session/utils/calling/CallManager'; import { getHasIncomingCall, getHasIncomingCallFrom } from '../../state/selectors/call'; import { Avatar, AvatarSize } from '../avatar/Avatar'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; import { SessionWrapperModal } from '../SessionWrapperModal'; +import { ed25519Str } from '../../session/utils/String'; export const CallWindow = styled.div` position: absolute; diff --git a/ts/components/conversation/composition/CompositionButtons.tsx b/ts/components/conversation/composition/CompositionButtons.tsx index 8a2a4d6657..3b145e2975 100644 --- a/ts/components/conversation/composition/CompositionButtons.tsx +++ b/ts/components/conversation/composition/CompositionButtons.tsx @@ -1,6 +1,5 @@ import React from 'react'; import styled from 'styled-components'; -import { Noop } from '../../../types/Util'; import { SessionIconButton } from '../../icon'; const StyledChatButtonContainer = styled.div` @@ -15,7 +14,7 @@ const StyledChatButtonContainer = styled.div` } `; -export const AddStagedAttachmentButton = (props: { onClick: Noop }) => { +export const AddStagedAttachmentButton = (props: { onClick: () => void }) => { return ( { ); }; -export const StartRecordingButton = (props: { onClick: Noop }) => { +export const StartRecordingButton = (props: { onClick: () => void }) => { return ( { }; // eslint-disable-next-line react/display-name -export const ToggleEmojiButton = React.forwardRef( +export const ToggleEmojiButton = React.forwardRef void }>( (props, ref) => { return ( @@ -70,7 +69,7 @@ export const ToggleEmojiButton = React.forwardRef { +export const SendMessageButton = (props: { onClick: () => void }) => { return ( { window?.log?.info('last message sent successfully. Deleting everything'); diff --git a/ts/components/dialog/EditProfilePictureModal.tsx b/ts/components/dialog/EditProfilePictureModal.tsx index 2554c420f3..7828d951bf 100644 --- a/ts/components/dialog/EditProfilePictureModal.tsx +++ b/ts/components/dialog/EditProfilePictureModal.tsx @@ -11,6 +11,7 @@ import { SessionSpinner } from '../basic/SessionSpinner'; import { SpacerLG } from '../basic/Text'; import { SessionIconButton } from '../icon'; import { ProfileAvatar } from './EditProfileDialog'; +import type { EditProfilePictureModalProps } from '../../types/ReduxTypes'; const StyledAvatarContainer = styled.div` cursor: pointer; @@ -59,12 +60,6 @@ const uploadProfileAvatar = async (scaledAvatarUrl: string | null) => { } }; -export type EditProfilePictureModalProps = { - avatarPath: string | null; - profileName: string | undefined; - ourId: string; -}; - export const EditProfilePictureModal = (props: EditProfilePictureModalProps) => { const dispatch = useDispatch(); diff --git a/ts/components/dialog/SessionPasswordDialog.tsx b/ts/components/dialog/SessionPasswordDialog.tsx index 98d7369021..7de8b4435a 100644 --- a/ts/components/dialog/SessionPasswordDialog.tsx +++ b/ts/components/dialog/SessionPasswordDialog.tsx @@ -11,8 +11,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S import { SessionWrapperModal } from '../SessionWrapperModal'; import { matchesHash, validatePassword } from '../../util/passwordUtils'; import { assertUnreachable } from '../../types/sqlSharedTypes'; - -export type PasswordAction = 'set' | 'change' | 'remove' | 'enter'; +import type { PasswordAction } from '../../types/ReduxTypes'; interface Props { passwordAction: PasswordAction; diff --git a/ts/components/leftpane/LeftPaneSettingSection.tsx b/ts/components/leftpane/LeftPaneSettingSection.tsx index b2dd84315b..8f154d157b 100644 --- a/ts/components/leftpane/LeftPaneSettingSection.tsx +++ b/ts/components/leftpane/LeftPaneSettingSection.tsx @@ -12,8 +12,8 @@ import { } from '../../state/ducks/section'; import { getFocusedSettingsSection } from '../../state/selectors/section'; import { SessionIcon } from '../icon'; -import { SessionSettingCategory } from '../settings/SessionSettings'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; +import type { SessionSettingCategory } from '../../types/ReduxTypes'; const StyledSettingsSectionTitle = styled.strong` font-family: var(--font-accent), var(--font-default); @@ -42,42 +42,42 @@ const StyledSettingsListItem = styled.div<{ active: boolean }>` } `; -const getCategories = () => { +const getCategories = (): Array<{ id: SessionSettingCategory; title: string }> => { return [ { - id: SessionSettingCategory.Privacy, + id: 'privacy' as const, title: window.i18n('privacySettingsTitle'), }, { - id: SessionSettingCategory.Notifications, + id: 'notifications' as const, title: window.i18n('notificationsSettingsTitle'), }, { - id: SessionSettingCategory.Conversations, + id: 'conversations' as const, title: window.i18n('conversationsSettingsTitle'), }, { - id: SessionSettingCategory.MessageRequests, + id: 'messageRequests' as const, title: window.i18n('openMessageRequestInbox'), }, { - id: SessionSettingCategory.Appearance, + id: 'appearance' as const, title: window.i18n('appearanceSettingsTitle'), }, { - id: SessionSettingCategory.Permissions, + id: 'permissions', title: window.i18n('permissionsSettingsTitle'), }, { - id: SessionSettingCategory.Help, + id: 'help' as const, title: window.i18n('helpSettingsTitle'), }, { - id: SessionSettingCategory.RecoveryPhrase, + id: 'recoveryPhrase' as const, title: window.i18n('recoveryPhrase'), }, { - id: SessionSettingCategory.ClearData, + id: 'clearData' as const, title: window.i18n('clearDataSettingsTitle'), }, ]; @@ -93,7 +93,7 @@ const LeftPaneSettingsCategoryRow = (props: { const dataTestId = `${title.toLowerCase().replace(' ', '-')}-settings-menu-item`; - const isClearData = id === SessionSettingCategory.ClearData; + const isClearData = id === 'clearData'; return ( { switch (id) { - case SessionSettingCategory.MessageRequests: + case 'messageRequests': dispatch(showLeftPaneSection(SectionType.Message)); dispatch(setLeftOverlayMode('message-requests')); dispatch(resetConversationExternal()); break; - case SessionSettingCategory.RecoveryPhrase: + case 'recoveryPhrase': dispatch(recoveryPhraseModal({})); break; - case SessionSettingCategory.ClearData: + case 'clearData': dispatch(updateDeleteAccountModal({})); break; default: diff --git a/ts/components/registration/SignUpTab.tsx b/ts/components/registration/SignUpTab.tsx index a54d0a01c3..9a5f27ab8f 100644 --- a/ts/components/registration/SignUpTab.tsx +++ b/ts/components/registration/SignUpTab.tsx @@ -7,7 +7,6 @@ import { RegistrationContext, RegistrationPhase, signUp } from './RegistrationSt import { RegistrationUserDetails } from './RegistrationUserDetails'; import { sanitizeDisplayNameOrToast, SignInMode } from './SignInTab'; import { TermsAndConditions } from './TermsAndConditions'; -import { Noop } from '../../types/Util'; export enum SignUpMode { Default, @@ -23,7 +22,7 @@ const ContinueSignUpButton = ({ continueSignUp }: { continueSignUp: any }) => { return ; }; -const SignUpDefault = (props: { createSessionID: Noop }) => { +const SignUpDefault = (props: { createSessionID: () => void }) => { return (
@@ -47,7 +46,7 @@ export const GoBackMainMenuButton = () => { ); }; -const SignUpSessionIDShown = (props: { continueSignUp: Noop }) => { +const SignUpSessionIDShown = (props: { continueSignUp: () => void }) => { return (
diff --git a/ts/components/settings/SessionSettingListItem.tsx b/ts/components/settings/SessionSettingListItem.tsx index 20311ff228..c2b111b231 100644 --- a/ts/components/settings/SessionSettingListItem.tsx +++ b/ts/components/settings/SessionSettingListItem.tsx @@ -10,7 +10,6 @@ import { import { SessionToggle } from '../basic/SessionToggle'; import { SessionConfirmDialogProps } from '../dialog/SessionConfirm'; import { SessionIconButton } from '../icon'; -import { Noop } from '../../types/Util'; type ButtonSettingsProps = { title?: string; @@ -113,7 +112,7 @@ export const SessionSettingsItemWrapper = (props: { ); }; -export const SessionSettingsTitleWithLink = (props: { title: string; onClick: Noop }) => { +export const SessionSettingsTitleWithLink = (props: { title: string; onClick: () => void }) => { const { onClick, title } = props; return ( diff --git a/ts/components/settings/SessionSettings.tsx b/ts/components/settings/SessionSettings.tsx index 99e4867d75..8c67da1a65 100644 --- a/ts/components/settings/SessionSettings.tsx +++ b/ts/components/settings/SessionSettings.tsx @@ -13,12 +13,12 @@ import { SessionNotificationGroupSettings } from './SessionNotificationGroupSett import { Data } from '../../data/data'; import { sessionPassword } from '../../state/ducks/modalDialog'; import { SectionType, showLeftPaneSection } from '../../state/ducks/section'; -import { PasswordAction } from '../dialog/SessionPasswordDialog'; import { SettingsCategoryAppearance } from './section/CategoryAppearance'; import { CategoryConversations } from './section/CategoryConversations'; import { SettingsCategoryHelp } from './section/CategoryHelp'; import { SettingsCategoryPermissions } from './section/CategoryPermissions'; import { SettingsCategoryPrivacy } from './section/CategoryPrivacy'; +import type { SessionSettingCategory, PasswordAction } from '../../types/ReduxTypes'; export function displayPasswordModal( passwordAction: PasswordAction, @@ -42,18 +42,6 @@ export function getCallMediaPermissionsSettings() { return window.getSettingValue('call-media-permissions'); } -export enum SessionSettingCategory { - Privacy = 'privacy', - Notifications = 'notifications', - Conversations = 'conversations', - MessageRequests = 'messageRequests', - Appearance = 'appearance', - Permissions = 'permissions', - Help = 'help', - RecoveryPhrase = 'recoveryPhrase', - ClearData = 'ClearData', -} - export interface SettingsViewProps { category: SessionSettingCategory; } @@ -113,25 +101,25 @@ const SettingInCategory = (props: { switch (category) { // special case for blocked user - case SessionSettingCategory.Conversations: + case 'conversations': return ; - case SessionSettingCategory.Appearance: + case 'appearance': return ; - case SessionSettingCategory.Notifications: + case 'notifications': return ; - case SessionSettingCategory.Privacy: + case 'privacy': return ( ); - case SessionSettingCategory.Help: + case 'help': return ; - case SessionSettingCategory.Permissions: + case 'permissions': return ; // these three down there have no options, they are just a button - case SessionSettingCategory.ClearData: - case SessionSettingCategory.MessageRequests: - case SessionSettingCategory.RecoveryPhrase: + case 'clearData': + case 'messageRequests': + case 'recoveryPhrase': default: return null; } diff --git a/ts/components/settings/SessionSettingsHeader.tsx b/ts/components/settings/SessionSettingsHeader.tsx index 2f6f357726..f2ed4646f9 100644 --- a/ts/components/settings/SessionSettingsHeader.tsx +++ b/ts/components/settings/SessionSettingsHeader.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; import { assertUnreachable } from '../../types/sqlSharedTypes'; -import { SessionSettingCategory, SettingsViewProps } from './SessionSettings'; +import { SettingsViewProps } from './SessionSettings'; type Props = Pick; @@ -26,27 +26,27 @@ export const SettingsHeader = (props: Props) => { let categoryTitle: string | null = null; switch (category) { - case SessionSettingCategory.Appearance: + case 'appearance': categoryTitle = window.i18n('appearanceSettingsTitle'); break; - case SessionSettingCategory.Conversations: + case 'conversations': categoryTitle = window.i18n('conversationsSettingsTitle'); break; - case SessionSettingCategory.Notifications: + case 'notifications': categoryTitle = window.i18n('notificationsSettingsTitle'); break; - case SessionSettingCategory.Help: + case 'help': categoryTitle = window.i18n('helpSettingsTitle'); break; - case SessionSettingCategory.Permissions: + case 'permissions': categoryTitle = window.i18n('permissionsSettingsTitle'); break; - case SessionSettingCategory.Privacy: + case 'privacy': categoryTitle = window.i18n('privacySettingsTitle'); break; - case SessionSettingCategory.ClearData: - case SessionSettingCategory.MessageRequests: - case SessionSettingCategory.RecoveryPhrase: + case 'clearData': + case 'messageRequests': + case 'recoveryPhrase': throw new Error(`no header for should be tried to be rendered for "${category}"`); default: diff --git a/ts/data/data.ts b/ts/data/data.ts index e5190e9970..ed585f7f37 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -109,6 +109,10 @@ async function updateSwarmNodesForPubkey( await channels.updateSwarmNodesForPubkey(pubkey, snodeEdKeys); } +async function clearOutAllSnodesNotInPool(edKeysOfSnodePool: Array): Promise { + await channels.clearOutAllSnodesNotInPool(edKeysOfSnodePool); +} + // Closed group /** @@ -802,6 +806,7 @@ export const Data = { generateAttachmentKeyIfEmpty, getSwarmNodesForPubkey, updateSwarmNodesForPubkey, + clearOutAllSnodesNotInPool, getAllEncryptionKeyPairsForGroup, getLatestClosedGroupEncryptionKeyPair, addClosedGroupEncryptionKeyPair, diff --git a/ts/data/dataInit.ts b/ts/data/dataInit.ts index abf1c3f43e..3577c9f548 100644 --- a/ts/data/dataInit.ts +++ b/ts/data/dataInit.ts @@ -24,6 +24,7 @@ const channelsToMake = new Set([ 'removeItemById', 'getSwarmNodesForPubkey', 'updateSwarmNodesForPubkey', + 'clearOutAllSnodesNotInPool', 'saveConversation', 'fetchConvoMemoryDetails', 'getConversationById', diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts index 828c059032..fae67ea3ab 100644 --- a/ts/interactions/conversations/unsendingInteractions.ts +++ b/ts/interactions/conversations/unsendingInteractions.ts @@ -9,12 +9,12 @@ import { SnodeAPI } from '../../session/apis/snode_api/SNodeAPI'; import { SnodeNamespaces } from '../../session/apis/snode_api/namespaces'; import { getConversationController } from '../../session/conversations'; import { UnsendMessage } from '../../session/messages/outgoing/controlMessage/UnsendMessage'; -import { ed25519Str } from '../../session/onions/onionPath'; import { PubKey } from '../../session/types'; import { ToastUtils, UserUtils } from '../../session/utils'; import { closeRightPanel, resetSelectedMessageIds } from '../../state/ducks/conversations'; import { updateConfirmModal } from '../../state/ducks/modalDialog'; import { resetRightOverlayMode } from '../../state/ducks/section'; +import { ed25519Str } from '../../session/utils/String'; /** * Deletes messages for everyone in a 1-1 or everyone in a closed group conversation. diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 096fd86d40..794a8c67e5 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -42,7 +42,7 @@ import { VisibleMessageParams, } from '../session/messages/outgoing/visibleMessage/VisibleMessage'; import { perfEnd, perfStart } from '../session/utils/Performance'; -import { toHex } from '../session/utils/String'; +import { ed25519Str, toHex } from '../session/utils/String'; import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout'; import { actions as conversationActions, @@ -74,7 +74,6 @@ import { MessageRequestResponse, MessageRequestResponseParams, } from '../session/messages/outgoing/controlMessage/MessageRequestResponse'; -import { ed25519Str } from '../session/onions/onionPath'; import { ConfigurationSync } from '../session/utils/job_runners/jobs/ConfigurationSyncJob'; import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts'; import { SessionUtilConvoInfoVolatile } from '../session/utils/libsession/libsession_utils_convo_info_volatile'; diff --git a/ts/node/logging.ts b/ts/node/logging.ts index f256d4f82d..1f141bedba 100644 --- a/ts/node/logging.ts +++ b/ts/node/logging.ts @@ -63,7 +63,7 @@ export async function initializeLogger() { fs.mkdirSync(logPath, { recursive: true }); console.info('fetching logs from logPath'); - fetch(logPath).then( + fetchLogFile(logPath).then( data => { event.sender.send('fetched-log', data); }, @@ -218,7 +218,7 @@ async function fetchLog(logFile: string) { }); } -export async function fetch(logPath: string) { +export async function fetchLogFile(logPath: string) { // Check that the file exists locally if (!fs.existsSync(logPath)) { (console as ConsoleCustom)._log( diff --git a/ts/node/menu.ts b/ts/node/menu.ts index fb45418505..f75b661ca3 100644 --- a/ts/node/menu.ts +++ b/ts/node/menu.ts @@ -1,6 +1,5 @@ import { isString } from 'lodash'; import { LocaleMessagesType } from './locale'; -import { Noop } from '../types/Util'; export const createTemplate = ( options: { @@ -157,7 +156,7 @@ export const createTemplate = ( function updateForMac( template: any, messages: LocaleMessagesType, - options: { showAbout: Noop; showWindow: Noop } + options: { showAbout: () => void; showWindow: () => void } ) { const { showAbout, showWindow } = options; diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 52323b03a8..80fcecbfd7 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -12,6 +12,7 @@ import { differenceBy, forEach, fromPairs, + intersection, isArray, isEmpty, isNumber, @@ -78,6 +79,7 @@ import { initDbInstanceWith, isInstanceInitialized, } from './sqlInstance'; +import { ed25519Str } from '../session/utils/String'; // eslint:disable: function-name non-literal-fs-path @@ -398,6 +400,32 @@ function updateSwarmNodesForPubkey(pubkey: string, snodeEdKeys: Array) { }); } +function clearOutAllSnodesNotInPool(edKeysOfSnodePool: Array) { + const allSwarms = assertGlobalInstance() + .prepare(`SELECT * FROM ${NODES_FOR_PUBKEY_TABLE};`) + .all(); + + allSwarms.forEach(swarm => { + try { + const json = JSON.parse(swarm.json); + if (isArray(json)) { + const intersect = intersection(json, edKeysOfSnodePool); + if (intersect.length !== json.length) { + updateSwarmNodesForPubkey(swarm.pubkey, intersect); + console.info( + `clearOutAllSnodesNotInPool: updating swarm of ${ed25519Str(swarm.pubkey)} to `, + intersect + ); + } + } + } catch (e) { + console.warn( + `Failed to parse swarm while iterating in clearOutAllSnodesNotInPool for pk: ${ed25519Str(swarm?.pubkey)}` + ); + } + }); +} + function getConversationCount() { const row = assertGlobalInstance().prepare(`SELECT count(*) from ${CONVERSATIONS_TABLE};`).get(); if (!row) { @@ -2449,6 +2477,7 @@ export const sqlNode = { getSwarmNodesForPubkey, updateSwarmNodesForPubkey, + clearOutAllSnodesNotInPool, getGuardNodes, updateGuardNodes, diff --git a/ts/session/apis/seed_node_api/SeedNodeAPI.ts b/ts/session/apis/seed_node_api/SeedNodeAPI.ts index 3a31aedff2..899b26f4d6 100644 --- a/ts/session/apis/seed_node_api/SeedNodeAPI.ts +++ b/ts/session/apis/seed_node_api/SeedNodeAPI.ts @@ -12,6 +12,7 @@ import { allowOnlyOneAtATime } from '../../utils/Promise'; import { APPLICATION_JSON } from '../../../types/MIME'; import { isLinux } from '../../../OS'; import { Snode } from '../../../data/data'; +import { GetServicesNodesFromSeedRequest } from '../snode_api/SnodeRequestTypes'; /** * Fetch all snodes from seed nodes. @@ -228,22 +229,20 @@ async function getSnodesFromSeedUrl(urlObj: URL): Promise> { // we get all active nodes window?.log?.info(`getSnodesFromSeedUrl starting with ${urlObj.href}`); - const params = { - active_only: true, - fields: { - public_ip: true, - storage_port: true, - pubkey_x25519: true, - pubkey_ed25519: true, - }, - }; - const endpoint = 'json_rpc'; const url = `${urlObj.href}${endpoint}`; - const body = { + const body: GetServicesNodesFromSeedRequest = { jsonrpc: '2.0', method: 'get_n_service_nodes', - params, + params: { + active_only: true, + fields: { + public_ip: true, + storage_port: true, + pubkey_x25519: true, + pubkey_ed25519: true, + }, + }, }; const sslAgent = await getSslAgentForSeedNode( diff --git a/ts/session/apis/snode_api/SNodeAPI.ts b/ts/session/apis/snode_api/SNodeAPI.ts index 2eeefde012..91f214e7c9 100644 --- a/ts/session/apis/snode_api/SNodeAPI.ts +++ b/ts/session/apis/snode_api/SNodeAPI.ts @@ -4,9 +4,8 @@ import { compact, sample } from 'lodash'; import pRetry from 'p-retry'; import { Snode } from '../../../data/data'; import { getSodiumRenderer } from '../../crypto'; -import { ed25519Str } from '../../onions/onionPath'; import { StringUtils, UserUtils } from '../../utils'; -import { fromBase64ToArray, fromHexToArray } from '../../utils/String'; +import { ed25519Str, fromBase64ToArray, fromHexToArray } from '../../utils/String'; import { doSnodeBatchRequest } from './batchRequest'; import { getSwarmFor } from './snodePool'; import { SnodeSignature } from './snodeSignatures'; diff --git a/ts/session/apis/snode_api/SnodeRequestTypes.ts b/ts/session/apis/snode_api/SnodeRequestTypes.ts index 1bed2be22b..ee2a7dbb74 100644 --- a/ts/session/apis/snode_api/SnodeRequestTypes.ts +++ b/ts/session/apis/snode_api/SnodeRequestTypes.ts @@ -67,19 +67,42 @@ export type OnsResolveSubRequest = { }; }; +/** + * If you are thinking of adding the `limit` field here: don't. + * We fetch the full list because we will remove from every cached swarms the snodes not found in that fresh list. + * If a `limit` was set, we would remove a lot of valid snodes from those cached swarms. + */ +type FetchSnodeListParams = { + active_only: true; + fields: { + public_ip: true; + storage_port: true; + pubkey_x25519: true; + pubkey_ed25519: true; + }; +}; + +export type GetServicesNodesFromSeedRequest = { + method: 'get_n_service_nodes'; + jsonrpc: '2.0'; + /** + * If you are thinking of adding the `limit` field here: don't. + * We fetch the full list because we will remove from every cached swarms the snodes not found in that fresh list. + * If the limit was set, we would remove a lot of valid snodes from the swarms we've already fetched. + */ + params: FetchSnodeListParams; +}; + export type GetServiceNodesSubRequest = { method: 'oxend_request'; params: { endpoint: 'get_service_nodes'; - params: { - active_only: true; - fields: { - public_ip: true; - storage_port: true; - pubkey_x25519: true; - pubkey_ed25519: true; - }; - }; + /** + * If you are thinking of adding the `limit` field here: don't. + * We fetch the full list because we will remove from every cached swarms the snodes not found in that fresh list. + * If the limit was set, we would remove a lot of valid snodes from the swarms we've already fetched. + */ + params: FetchSnodeListParams; }; }; diff --git a/ts/session/apis/snode_api/onions.ts b/ts/session/apis/snode_api/onions.ts index 1478bce85f..9aedbea6d6 100644 --- a/ts/session/apis/snode_api/onions.ts +++ b/ts/session/apis/snode_api/onions.ts @@ -11,8 +11,8 @@ import { AbortSignal as AbortSignalNode } from 'node-fetch/externals'; import { dropSnodeFromSnodePool, dropSnodeFromSwarmIfNeeded, updateSwarmFor } from './snodePool'; import { OnionPaths } from '../../onions'; -import { ed25519Str, incrementBadPathCountOrDrop } from '../../onions/onionPath'; -import { toHex } from '../../utils/String'; +import { incrementBadPathCountOrDrop } from '../../onions/onionPath'; +import { ed25519Str, toHex } from '../../utils/String'; import { Snode } from '../../../data/data'; import { callUtilsWorker } from '../../../webworker/workers/browser/util_worker_interface'; diff --git a/ts/session/apis/snode_api/retrieveRequest.ts b/ts/session/apis/snode_api/retrieveRequest.ts index f8c9d9bdeb..50d94eadac 100644 --- a/ts/session/apis/snode_api/retrieveRequest.ts +++ b/ts/session/apis/snode_api/retrieveRequest.ts @@ -5,7 +5,7 @@ import { doSnodeBatchRequest } from './batchRequest'; import { GetNetworkTime } from './getNetworkTime'; import { SnodeNamespace, SnodeNamespaces } from './namespaces'; -import { TTL_DEFAULT } from '../../constants'; +import { DURATION, TTL_DEFAULT } from '../../constants'; import { UserUtils } from '../../utils'; import { sleepFor } from '../../utils/Promise'; import { @@ -124,7 +124,7 @@ async function retrieveNextMessages( ); // let exceptions bubble up // no retry for this one as this a call we do every few seconds while polling for messages - const timeOutMs = 4 * 1000; + const timeOutMs = 10 * DURATION.SECONDS; // yes this is a long timeout for just messages, but 4s timeouts way to often... const timeoutPromise = async () => sleepFor(timeOutMs); const fetchPromise = async () => doSnodeBatchRequest(retrieveRequestsParams, targetNode, timeOutMs, associatedWith); @@ -166,7 +166,8 @@ async function retrieveNextMessages( GetNetworkTime.handleTimestampOffsetFromNetwork('retrieve', bodyFirstResult.t); - // merge results with their corresponding namespaces + // NOTE: We don't want to sort messages here because the ordering depends on the snode and when it received each message. + // The last_hash for that snode has to be the last one we've received from that same snode, othwerwise we end up fetching the same messages over and over again. return results.map((result, index) => ({ code: result.code, messages: result.body as RetrieveMessagesResultsContent, diff --git a/ts/session/apis/snode_api/snodePool.ts b/ts/session/apis/snode_api/snodePool.ts index f13a27e218..20a029d2ee 100644 --- a/ts/session/apis/snode_api/snodePool.ts +++ b/ts/session/apis/snode_api/snodePool.ts @@ -3,12 +3,12 @@ import pRetry from 'p-retry'; import { Data, Snode } from '../../../data/data'; -import { ed25519Str } from '../../onions/onionPath'; import { OnionPaths } from '../../onions'; import { Onions, SnodePool } from '.'; import { SeedNodeAPI } from '../seed_node_api'; import { requestSnodesForPubkeyFromNetwork } from './getSwarmFor'; import { ServiceNodesList } from './getServiceNodesList'; +import { ed25519Str } from '../../utils/String'; /** * If we get less than this snode in a swarm, we fetch new snodes for this pubkey @@ -204,6 +204,18 @@ export async function TEST_fetchFromSeedWithRetriesAndWriteToDb() { } } +async function clearOutAllSnodesNotInPool(snodePool: Array) { + if (snodePool.length <= 10) { + return; + } + const edKeysOfSnodePool = snodePool.map(m => m.pubkey_ed25519); + + await Data.clearOutAllSnodesNotInPool(edKeysOfSnodePool); + + // just remove all the cached entries, we will refetch them as needed from the DB + swarmCache.clear(); +} + /** * This function retries a few times to get a consensus between 3 snodes of at least 24 snodes in the snode pool. * @@ -230,6 +242,7 @@ async function tryToGetConsensusWithSnodesWithRetries() { ); randomSnodePool = commonNodes; await Data.updateSnodePoolOnDb(JSON.stringify(randomSnodePool)); + await clearOutAllSnodesNotInPool(randomSnodePool); OnionPaths.resetPathFailureCount(); Onions.resetSnodeFailureCount(); diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index e8ddcc092f..cedebc9378 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable more/no-then */ /* eslint-disable @typescript-eslint/no-misused-promises */ -import { compact, concat, difference, flatten, last, sample, toNumber, uniqBy } from 'lodash'; +import { compact, concat, flatten, last, sample, toNumber, uniqBy } from 'lodash'; import { Data, Snode } from '../../../data/data'; import { SignalService } from '../../../protobuf'; import * as Receiver from '../../../receiver/receiver'; @@ -22,13 +22,12 @@ import { import { DURATION, SWARM_POLLING_TIMEOUT } from '../../constants'; import { getConversationController } from '../../conversations'; import { IncomingMessage } from '../../messages/incoming/IncomingMessage'; -import { ed25519Str } from '../../onions/onionPath'; import { StringUtils, UserUtils } from '../../utils'; -import { perfEnd, perfStart } from '../../utils/Performance'; import { LibSessionUtil } from '../../utils/libsession/libsession_utils'; import { SnodeNamespace, SnodeNamespaces } from './namespaces'; import { SnodeAPIRetrieve } from './retrieveRequest'; import { RetrieveMessageItem, RetrieveMessagesResultsBatched } from './types'; +import { ed25519Str } from '../../utils/String'; export function extractWebSocketContent( message: string, @@ -228,21 +227,16 @@ export class SwarmPolling { namespaces: Array ) { const polledPubkey = pubkey.key; + let resultsFromAllNamespaces: RetrieveMessagesResultsBatched | null; const swarmSnodes = await snodePool.getSwarmFor(polledPubkey); - - // Select nodes for which we already have lastHashes - const alreadyPolled = swarmSnodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]); - let toPollFrom = alreadyPolled.length ? alreadyPolled[0] : null; - - // If we need more nodes, select randomly from the remaining nodes: - if (!toPollFrom) { - const notPolled = difference(swarmSnodes, alreadyPolled); - toPollFrom = sample(notPolled) as Snode; - } - - let resultsFromAllNamespaces: RetrieveMessagesResultsBatched | null; + let toPollFrom: Snode | undefined; try { + toPollFrom = sample(swarmSnodes); + + if (!toPollFrom) { + throw new Error(`pollOnceForKey: no snode in swarm for ${ed25519Str(polledPubkey)}`); + } // Note: always print something so we know if the polling is hanging window.log.info( `about to pollNodeForKey of ${ed25519Str(pubkey.key)} from snode: ${ed25519Str(toPollFrom.pubkey_ed25519)} namespaces: ${namespaces} ` @@ -337,9 +331,10 @@ export class SwarmPolling { }); } - perfStart(`handleSeenMessages-${polledPubkey}`); const newMessages = await this.handleSeenMessages(messages); - perfEnd(`handleSeenMessages-${polledPubkey}`, 'handleSeenMessages'); + window.log.info( + `handleSeenMessages: ${newMessages.length} out of ${messages.length} are not seen yet. snode: ${toPollFrom ? ed25519Str(toPollFrom.pubkey_ed25519) : 'undefined'}` + ); // don't handle incoming messages from group swarms when using the userconfig and the group is not one of the tracked group const isUserConfigReleaseLive = await ReleasedFeatures.checkIsUserConfigFeatureReleased(); diff --git a/ts/session/onions/onionPath.ts b/ts/session/onions/onionPath.ts index d05229976d..84b9d14a18 100644 --- a/ts/session/onions/onionPath.ts +++ b/ts/session/onions/onionPath.ts @@ -14,6 +14,7 @@ import { updateOnionPaths } from '../../state/ducks/onion'; import { ERROR_CODE_NO_CONNECT } from '../apis/snode_api/SNodeAPI'; import { OnionPaths } from '.'; import { APPLICATION_JSON } from '../../types/MIME'; +import { ed25519Str } from '../utils/String'; const desiredGuardCount = 3; const minimumGuardCount = 2; @@ -63,8 +64,6 @@ const pathFailureThreshold = 3; // some naming issue here it seems) export let guardNodes: Array = []; -export const ed25519Str = (ed25519Key: string) => `(...${ed25519Key.substr(58)})`; - export async function buildNewOnionPathsOneAtATime() { // this function may be called concurrently make sure we only have one inflight return allowOnlyOneAtATime('buildNewOnionPaths', async () => { diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index e2dcdf4ab5..b7cbac6016 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -32,11 +32,10 @@ import { SharedConfigMessage } from '../messages/outgoing/controlMessage/SharedC import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage'; import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage'; import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage'; -import { ed25519Str } from '../onions/onionPath'; import { PubKey } from '../types'; import { RawMessage } from '../types/RawMessage'; import { UserUtils } from '../utils'; -import { fromUInt8ArrayToBase64 } from '../utils/String'; +import { ed25519Str, fromUInt8ArrayToBase64 } from '../utils/String'; import { EmptySwarmError } from '../utils/errors'; // ================ SNODE STORE ================ diff --git a/ts/session/utils/String.ts b/ts/session/utils/String.ts index b12f099715..68713e5e5d 100644 --- a/ts/session/utils/String.ts +++ b/ts/session/utils/String.ts @@ -71,3 +71,5 @@ export const sanitizeSessionUsername = (inputName: string) => { return validChars; }; + +export const ed25519Str = (ed25519Key: string) => `(...${ed25519Key.substr(58)})`; diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx index 7e7be394bd..a6673aa594 100644 --- a/ts/session/utils/Toast.tsx +++ b/ts/session/utils/Toast.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { toast } from 'react-toastify'; import { SessionToast, SessionToastType } from '../../components/basic/SessionToast'; -import { SessionSettingCategory } from '../../components/settings/SessionSettings'; import { SectionType, showLeftPaneSection, showSettingsSection } from '../../state/ducks/section'; // if you push a toast manually with toast...() be sure to set the type attribute of the SessionToast component @@ -132,7 +131,7 @@ export function pushedMissedCall(conversationName: string) { const openPermissionsSettings = () => { window.inboxStore?.dispatch(showLeftPaneSection(SectionType.Settings)); - window.inboxStore?.dispatch(showSettingsSection(SessionSettingCategory.Permissions)); + window.inboxStore?.dispatch(showSettingsSection('permissions')); }; export function pushedMissedCallCauseOfPermission(conversationName: string) { diff --git a/ts/session/utils/calling/CallManager.ts b/ts/session/utils/calling/CallManager.ts index cc411f0aa8..0c04cdaf76 100644 --- a/ts/session/utils/calling/CallManager.ts +++ b/ts/session/utils/calling/CallManager.ts @@ -18,7 +18,6 @@ import { import { openConversationWithMessages } from '../../../state/ducks/conversations'; import { getConversationController } from '../../conversations'; import { CallMessage } from '../../messages/outgoing/controlMessage/CallMessage'; -import { ed25519Str } from '../../onions/onionPath'; import { PubKey } from '../../types'; import { getMessageQueue } from '../..'; @@ -35,6 +34,7 @@ import { ReadyToDisappearMsgUpdate } from '../../disappearing_messages/types'; import { MessageSender } from '../../sending'; import { getIsRinging } from '../RingingManager'; import { getBlackSilenceMediaStream } from './Silence'; +import { ed25519Str } from '../String'; export type InputItem = { deviceId: string; label: string }; @@ -415,8 +415,11 @@ async function createOfferAndSendIt(recipient: string, msgIdentifier: string | n if (offer && offer.sdp) { const lines = offer.sdp.split(/\r?\n/); const lineWithFtmpIndex = lines.findIndex(f => f.startsWith('a=fmtp:111')); - const partBeforeComma = lines[lineWithFtmpIndex].split(';'); - lines[lineWithFtmpIndex] = `${partBeforeComma[0]};cbr=1`; + // If webrtc does not find any audio input when initializing, the offer will not have a line with `a=fmtp:111` at all, `lineWithFtmpIndex` will be invalid. + if (lineWithFtmpIndex > -1) { + const partBeforeComma = lines[lineWithFtmpIndex].split(';'); + lines[lineWithFtmpIndex] = `${partBeforeComma[0]};cbr=1`; + } let overridenSdps = lines.join('\n'); overridenSdps = overridenSdps.replace( // eslint-disable-next-line prefer-regex-literals diff --git a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts index aaae284d15..0e94040c08 100644 --- a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts +++ b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts @@ -1,14 +1,18 @@ /* eslint-disable no-await-in-loop */ +import { to_hex } from 'libsodium-wrappers-sumo'; import { compact, isArray, isEmpty, isNumber, isString } from 'lodash'; import { v4 } from 'uuid'; import { UserUtils } from '../..'; import { ConfigDumpData } from '../../../../data/configDump/configDump'; import { ConfigurationSyncJobDone } from '../../../../shims/events'; +import { ReleasedFeatures } from '../../../../util/releaseFeature'; +import { isSignInByLinking } from '../../../../util/storage'; import { GenericWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface'; import { NotEmptyArrayOfBatchResults } from '../../../apis/snode_api/SnodeRequestTypes'; import { getConversationController } from '../../../conversations'; import { SharedConfigMessage } from '../../../messages/outgoing/controlMessage/SharedConfigMessage'; import { MessageSender } from '../../../sending/MessageSender'; +import { allowOnlyOneAtATime } from '../../Promise'; import { LibSessionUtil, OutgoingConfResult } from '../../libsession/libsession_utils'; import { runners } from '../JobRunner'; import { @@ -17,9 +21,6 @@ import { PersistedJob, RunJobResult, } from '../PersistedJob'; -import { ReleasedFeatures } from '../../../../util/releaseFeature'; -import { allowOnlyOneAtATime } from '../../Promise'; -import { isSignInByLinking } from '../../../../util/storage'; const defaultMsBetweenRetries = 15000; // a long time between retries, to avoid running multiple jobs at the same time, when one was postponed at the same time as one already planned (5s) const defaultMaxAttempts = 2; @@ -208,6 +209,29 @@ class ConfigurationSyncJob extends PersistedJob }; }); + if (window.sessionFeatureFlags.debug.debugLibsessionDumps) { + for (let index = 0; index < LibSessionUtil.requiredUserVariants.length; index++) { + const variant = LibSessionUtil.requiredUserVariants[index]; + + window.log.info( + `ConfigurationSyncJob: current dumps: ${variant}:`, + to_hex(await GenericWrapperActions.dump(variant)) + ); + } + window.log.info( + 'ConfigurationSyncJob: About to push changes: ', + msgs.map(m => { + return { + ...m, + message: { + ...m.message, + data: to_hex(m.message.data), + }, + }; + }) + ); + } + const result = await MessageSender.sendMessagesToSnode( msgs, thisJobDestination, diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts index 097a7ccfd8..a9c72dace3 100644 --- a/ts/state/createStore.ts +++ b/ts/state/createStore.ts @@ -23,7 +23,7 @@ const logger = createLogger({ logger: directConsole, }); -export const persistConfig = { +const persistConfig = { key: 'root', storage, whitelist: ['userConfig'], diff --git a/ts/state/ducks/modalDialog.tsx b/ts/state/ducks/modalDialog.tsx index 187d5fe2b3..715c8c5c05 100644 --- a/ts/state/ducks/modalDialog.tsx +++ b/ts/state/ducks/modalDialog.tsx @@ -1,8 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { EditProfilePictureModalProps } from '../../components/dialog/EditProfilePictureModal'; import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm'; -import { PasswordAction } from '../../components/dialog/SessionPasswordDialog'; -import { Noop } from '../../types/Util'; +import type { EditProfilePictureModalProps, PasswordAction } from '../../types/ReduxTypes'; export type BanType = 'ban' | 'unban'; @@ -23,7 +21,7 @@ export type OnionPathModalState = EditProfileModalState; export type RecoveryPhraseModalState = EditProfileModalState; export type DeleteAccountModalState = EditProfileModalState; -export type SessionPasswordModalState = { passwordAction: PasswordAction; onOk: Noop } | null; +export type SessionPasswordModalState = { passwordAction: PasswordAction; onOk: () => void } | null; export type UserDetailsModalState = { conversationId: string; diff --git a/ts/state/ducks/section.tsx b/ts/state/ducks/section.tsx index 5060af4cca..27cbe1cb76 100644 --- a/ts/state/ducks/section.tsx +++ b/ts/state/ducks/section.tsx @@ -1,5 +1,6 @@ -// TODOLATER move into redux slice -import { SessionSettingCategory } from '../../components/settings/SessionSettings'; +// TODO move into redux slice + +import type { SessionSettingCategory } from '../../types/ReduxTypes'; export const FOCUS_SECTION = 'FOCUS_SECTION'; export const FOCUS_SETTINGS_SECTION = 'FOCUS_SETTINGS_SECTION'; @@ -173,7 +174,7 @@ export const reducer = ( return { ...state, focusedSection: payload, - focusedSettingsSection: SessionSettingCategory.Privacy, + focusedSettingsSection: 'privacy', }; case FOCUS_SETTINGS_SECTION: return { diff --git a/ts/state/ducks/settings.tsx b/ts/state/ducks/settings.tsx index 318e7fc234..7397b47c37 100644 --- a/ts/state/ducks/settings.tsx +++ b/ts/state/ducks/settings.tsx @@ -2,7 +2,6 @@ import { isBoolean } from 'lodash'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { SettingsKey } from '../../data/settings-key'; -import { Storage } from '../../util/storage'; const SettingsBoolsKeyTrackedInRedux = [ SettingsKey.someDeviceOutdatedSyncing, @@ -44,33 +43,31 @@ const settingsSlice = createSlice({ // Once the storage is ready, initialState: getSettingsInitialState(), reducers: { - updateAllOnStorageReady(state) { - const linkPreview = Storage.get(SettingsKey.settingsLinkPreview, false); - const outdatedSync = Storage.get(SettingsKey.someDeviceOutdatedSyncing, false); - const hasBlindedMsgRequestsEnabled = Storage.get( - SettingsKey.hasBlindedMsgRequestsEnabled, - false - ); - const hasFollowSystemThemeEnabled = Storage.get( - SettingsKey.hasFollowSystemThemeEnabled, - false - ); - const hasShiftSendEnabled = Storage.get(SettingsKey.hasShiftSendEnabled, false); - state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync) - ? outdatedSync - : false; - state.settingsBools['link-preview-setting'] = isBoolean(linkPreview) ? linkPreview : false; // this is the value of SettingsKey.settingsLinkPreview - state.settingsBools.hasBlindedMsgRequestsEnabled = isBoolean(hasBlindedMsgRequestsEnabled) - ? hasBlindedMsgRequestsEnabled - : false; + updateAllOnStorageReady( + state, + { + payload, + }: PayloadAction<{ + settingsLinkPreview: boolean; + someDeviceOutdatedSyncing: boolean; + hasBlindedMsgRequestsEnabled: boolean; + hasFollowSystemThemeEnabled: boolean; + hasShiftSendEnabled: boolean; + }> + ) { + const { + hasBlindedMsgRequestsEnabled, + hasFollowSystemThemeEnabled, + settingsLinkPreview, + someDeviceOutdatedSyncing, + hasShiftSendEnabled, + } = payload; - state.settingsBools.hasFollowSystemThemeEnabled = isBoolean(hasFollowSystemThemeEnabled) - ? hasFollowSystemThemeEnabled - : false; - - state.settingsBools.hasShiftSendEnabled = isBoolean(hasShiftSendEnabled) - ? hasShiftSendEnabled - : false; + state.settingsBools.someDeviceOutdatedSyncing = someDeviceOutdatedSyncing; + state.settingsBools['link-preview-setting'] = settingsLinkPreview; + state.settingsBools.hasBlindedMsgRequestsEnabled = hasBlindedMsgRequestsEnabled; + state.settingsBools.hasFollowSystemThemeEnabled = hasFollowSystemThemeEnabled; + state.settingsBools.hasShiftSendEnabled = hasShiftSendEnabled; return state; }, diff --git a/ts/state/ducks/sogsRoomInfo.tsx b/ts/state/ducks/sogsRoomInfo.tsx index 6ebe3c6ce4..df8821d38b 100644 --- a/ts/state/ducks/sogsRoomInfo.tsx +++ b/ts/state/ducks/sogsRoomInfo.tsx @@ -1,9 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { isFinite, sortBy, uniq, xor } from 'lodash'; -import { - getCanWriteOutsideRedux, - getCurrentSubscriberCountOutsideRedux, -} from '../selectors/sogsRoomInfo'; type RoomInfo = { canWrite: boolean; @@ -79,16 +75,10 @@ export const ReduxSogsRoomInfos = { }; function setSubscriberCountOutsideRedux(convoId: string, subscriberCount: number) { - if (subscriberCount === getCurrentSubscriberCountOutsideRedux(convoId)) { - return; - } window.inboxStore?.dispatch(setSubscriberCount({ convoId, subscriberCount })); } function setCanWriteOutsideRedux(convoId: string, canWrite: boolean) { - if (getCanWriteOutsideRedux(convoId) === canWrite) { - return; - } window.inboxStore?.dispatch(setCanWrite({ convoId, canWrite })); } diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index eb382c372b..2e9b1a9d36 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -37,7 +37,7 @@ export type StateType = { settings: SettingsState; }; -export const reducers = { +const reducers = { search, conversations, user, diff --git a/ts/state/selectors/section.ts b/ts/state/selectors/section.ts index 9599f35860..39992174e2 100644 --- a/ts/state/selectors/section.ts +++ b/ts/state/selectors/section.ts @@ -1,8 +1,8 @@ import { createSelector } from '@reduxjs/toolkit'; -import { SessionSettingCategory } from '../../components/settings/SessionSettings'; import { LeftOverlayMode, SectionStateType, SectionType } from '../ducks/section'; import { StateType } from '../reducer'; +import type { SessionSettingCategory } from '../../types/ReduxTypes'; export const getSection = (state: StateType): SectionStateType => state.section; diff --git a/ts/types/ReduxTypes.d.ts b/ts/types/ReduxTypes.d.ts new file mode 100644 index 0000000000..d744b0f140 --- /dev/null +++ b/ts/types/ReduxTypes.d.ts @@ -0,0 +1,23 @@ +/** + * Note: The types defined in this file have to be self contained. + * We must not import anything in this file, especially not something relying on the window object (even indirectly, through an import chain). + */ + +export type SessionSettingCategory = + | 'privacy' + | 'notifications' + | 'conversations' + | 'messageRequests' + | 'appearance' + | 'permissions' + | 'help' + | 'recoveryPhrase' + | 'clearData'; + +export type PasswordAction = 'set' | 'change' | 'remove' | 'enter'; + +export type EditProfilePictureModalProps = { + avatarPath: string | null; + profileName: string | undefined; + ourId: string; +}; diff --git a/ts/types/Util.ts b/ts/types/Util.ts index 76f1d68008..ccea6b792a 100644 --- a/ts/types/Util.ts +++ b/ts/types/Util.ts @@ -7,5 +7,3 @@ export type RenderTextCallbackType = (options: { }) => JSX.Element; export type LocalizerType = (key: LocalizerKeys, values?: Array) => string; - -export type Noop = () => void; diff --git a/ts/util/logging.ts b/ts/util/logging.ts index 83c4945808..b82cfd8263 100644 --- a/ts/util/logging.ts +++ b/ts/util/logging.ts @@ -94,7 +94,7 @@ function format(entries: Array) { return redactAll(entries.map(formatLine).join('\n')); } -export async function fetch() { +export async function fetchNodeLog() { return new Promise(resolve => { ipc.on('fetched-log', (_event, text) => { const result = `${getHeader()}\n${format(text)}`; diff --git a/ts/util/storage.ts b/ts/util/storage.ts index ba229323ce..5710d30bcd 100644 --- a/ts/util/storage.ts +++ b/ts/util/storage.ts @@ -160,4 +160,12 @@ export async function saveRecentReations(reactions: Array) { return Storage.put('recent_reactions', reactions.join(' ')); } -export const Storage = { fetch, put, get, remove, onready, reset }; +function getBoolOrFalse(settingsKey: string): boolean { + const got = Storage.get(settingsKey, false); + if (isBoolean(got)) { + return got; + } + return false; +} + +export const Storage = { fetch, put, get, getBoolOrFalse, remove, onready, reset };