Compare commits

...

13 Commits

Author SHA1 Message Date
Audric Ackermann 2d871ef0d9 chore: bump to Session 1.12.3 3 weeks ago
Audric Ackermann 4186c29fe1
Merge pull request #3090 from oxen-io/unstable
fix retrieve issue
3 weeks ago
Audric Ackermann 4eafe8c68c
Merge pull request #3080 from Bilb/fix-retrieve-snode-down
fix: randomly pick a snode to poll from until we build a better way with changes server side
3 weeks ago
Audric Ackermann 51c307af25 chore: fix PR reviews 3 weeks ago
Audric Ackermann faa24ce9a6 fix: lint 3 weeks ago
Audric Ackermann 42bea0264c fix: improve retrieve timeout to 10s
also:
- add comments about not adding the limit:256 on snode list fetch
- fix an issue when no audio are found when starting a webrtc call
3 weeks ago
Audric Ackermann 4589bde672 chore: moved more types to ReduxTypes.d.ts 3 weeks ago
Audric Ackermann fb99c3491c chore: remove calls to Storage from settings.tsx 3 weeks ago
Audric Ackermann 49ab04d2fd chore: move SessionSettingsCategory to .d.ts file
and remove the enum (instead use string union)
3 weeks ago
Audric Ackermann 7f7f0fe26c fix: clear swarms from snodes not in pool on full fetch 4 weeks ago
Audric Ackermann 48a245e13c
Merge pull request #3082 from Aerilym/chore/rename_fetch_functions
Rename fetch functions
4 weeks ago
Ryan Miller 87fb1701d4
chore: rename fetch functions 4 weeks ago
Audric Ackermann 52ebcfdbab fix: randomly pick a snode topollfrom until we build a better way 1 month ago

2
.gitignore vendored

@ -53,3 +53,5 @@ stylesheets/dist/
*.LICENSE.txt
ts/webworker/workers/node/**/*.node
.yarn/**/*.mjs
.yarn/**/*.cjs

@ -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",

@ -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);

@ -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();
}

@ -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' };

@ -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;

@ -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 (
<StyledChatButtonContainer>
<SessionIconButton
@ -32,7 +31,7 @@ export const AddStagedAttachmentButton = (props: { onClick: Noop }) => {
);
};
export const StartRecordingButton = (props: { onClick: Noop }) => {
export const StartRecordingButton = (props: { onClick: () => void }) => {
return (
<StyledChatButtonContainer>
<SessionIconButton
@ -50,7 +49,7 @@ export const StartRecordingButton = (props: { onClick: Noop }) => {
};
// eslint-disable-next-line react/display-name
export const ToggleEmojiButton = React.forwardRef<HTMLButtonElement, { onClick: Noop }>(
export const ToggleEmojiButton = React.forwardRef<HTMLButtonElement, { onClick: () => void }>(
(props, ref) => {
return (
<StyledChatButtonContainer>
@ -70,7 +69,7 @@ export const ToggleEmojiButton = React.forwardRef<HTMLButtonElement, { onClick:
}
);
export const SendMessageButton = (props: { onClick: Noop }) => {
export const SendMessageButton = (props: { onClick: () => void }) => {
return (
<StyledChatButtonContainer className="send-message-button">
<SessionIconButton

@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
import { SnodeAPI } from '../../session/apis/snode_api/SNodeAPI';
import { ed25519Str } from '../../session/onions/onionPath';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/sync/syncUtils';
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import { SessionWrapperModal } from '../SessionWrapperModal';
@ -14,6 +14,7 @@ import { deleteAllLogs } from '../../node/logs';
import { clearInbox } from '../../session/apis/open_group_api/sogsv3/sogsV3ClearInbox';
import { getAllValidOpenGroupV2ConversationRoomInfos } from '../../session/apis/open_group_api/utils/OpenGroupUtils';
import { SessionRadioGroup } from '../basic/SessionRadioGroup';
import { ed25519Str } from '../../session/utils/String';
const deleteDbLocally = async () => {
window?.log?.info('last message sent successfully. Deleting everything');

@ -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();

@ -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;

@ -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 (
<StyledSettingsListItem
@ -103,15 +103,15 @@ const LeftPaneSettingsCategoryRow = (props: {
role="link"
onClick={() => {
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:

@ -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 <SessionButton onClick={continueSignUp} text={window.i18n('continue')} />;
};
const SignUpDefault = (props: { createSessionID: Noop }) => {
const SignUpDefault = (props: { createSessionID: () => void }) => {
return (
<div className="session-registration__content">
<CreateSessionIdButton createSessionID={props.createSessionID} />
@ -47,7 +46,7 @@ export const GoBackMainMenuButton = () => {
);
};
const SignUpSessionIDShown = (props: { continueSignUp: Noop }) => {
const SignUpSessionIDShown = (props: { continueSignUp: () => void }) => {
return (
<div className="session-registration__content">
<Flex flexDirection="row" container={true} alignItems="center">

@ -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 (
<StyledSettingItemClickable onClick={onClick}>

@ -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 <CategoryConversations />;
case SessionSettingCategory.Appearance:
case 'appearance':
return <SettingsCategoryAppearance />;
case SessionSettingCategory.Notifications:
case 'notifications':
return <SessionNotificationGroupSettings />;
case SessionSettingCategory.Privacy:
case 'privacy':
return (
<SettingsCategoryPrivacy onPasswordUpdated={onPasswordUpdated} hasPassword={hasPassword} />
);
case SessionSettingCategory.Help:
case 'help':
return <SettingsCategoryHelp />;
case SessionSettingCategory.Permissions:
case 'permissions':
return <SettingsCategoryPermissions />;
// 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;
}

@ -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<SettingsViewProps, 'category'>;
@ -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:

@ -109,6 +109,10 @@ async function updateSwarmNodesForPubkey(
await channels.updateSwarmNodesForPubkey(pubkey, snodeEdKeys);
}
async function clearOutAllSnodesNotInPool(edKeysOfSnodePool: Array<string>): Promise<void> {
await channels.clearOutAllSnodesNotInPool(edKeysOfSnodePool);
}
// Closed group
/**
@ -802,6 +806,7 @@ export const Data = {
generateAttachmentKeyIfEmpty,
getSwarmNodesForPubkey,
updateSwarmNodesForPubkey,
clearOutAllSnodesNotInPool,
getAllEncryptionKeyPairsForGroup,
getLatestClosedGroupEncryptionKeyPair,
addClosedGroupEncryptionKeyPair,

@ -24,6 +24,7 @@ const channelsToMake = new Set([
'removeItemById',
'getSwarmNodesForPubkey',
'updateSwarmNodesForPubkey',
'clearOutAllSnodesNotInPool',
'saveConversation',
'fetchConvoMemoryDetails',
'getConversationById',

@ -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.

@ -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';

@ -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(

@ -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;

@ -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<string>) {
});
}
function clearOutAllSnodesNotInPool(edKeysOfSnodePool: Array<string>) {
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,

@ -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<Array<any>> {
// 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(

@ -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';

@ -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;
};
};

@ -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';

@ -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,

@ -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<Snode>) {
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();

@ -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<SnodeNamespaces>
) {
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();

@ -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<Snode> = [];
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 () => {

@ -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 ================

@ -71,3 +71,5 @@ export const sanitizeSessionUsername = (inputName: string) => {
return validChars;
};
export const ed25519Str = (ed25519Key: string) => `(...${ed25519Key.substr(58)})`;

@ -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) {

@ -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

@ -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<ConfigurationSyncPersistedData>
};
});
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,

@ -23,7 +23,7 @@ const logger = createLogger({
logger: directConsole,
});
export const persistConfig = {
const persistConfig = {
key: 'root',
storage,
whitelist: ['userConfig'],

@ -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;

@ -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 {

@ -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;
},

@ -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 }));
}

@ -37,7 +37,7 @@ export type StateType = {
settings: SettingsState;
};
export const reducers = {
const reducers = {
search,
conversations,
user,

@ -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;

@ -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;
};

@ -7,5 +7,3 @@ export type RenderTextCallbackType = (options: {
}) => JSX.Element;
export type LocalizerType = (key: LocalizerKeys, values?: Array<string>) => string;
export type Noop = () => void;

@ -94,7 +94,7 @@ function format(entries: Array<EntryType>) {
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)}`;

@ -160,4 +160,12 @@ export async function saveRecentReations(reactions: Array<string>) {
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 };

Loading…
Cancel
Save