chore: add a redux settings slice, currently outdated banner inc

This commit is contained in:
Audric Ackermann 2023-04-24 14:34:04 +10:00
parent 0080254286
commit 0e286142f1
33 changed files with 232 additions and 132 deletions

View file

@ -43,10 +43,11 @@ moment.locale((window.i18n as any).getLocale());
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
import useUpdate from 'react-use/lib/useUpdate';
import useInterval from 'react-use/lib/useInterval';
import { SettingsKey } from '../data/settings-key';
import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex';
import { useHasDeviceOutdatedSyncing } from '../state/selectors/settings';
import { getSettingsInitialState, updateAllOnStorageReady } from '../state/ducks/settings';
const StyledGutter = styled.div`
width: 380px !important;
@ -60,7 +61,6 @@ function createSessionInboxStore() {
.map(conversation => conversation.getConversationModelProps());
const timerOptions: TimerOptionsArray = ExpirationTimerOptions.getTimerSecondsWithName();
const initialState: StateType = {
conversations: {
...getEmptyConversationState(),
@ -83,6 +83,7 @@ function createSessionInboxStore() {
stagedAttachments: getEmptyStagedAttachmentsState(),
call: initialCallState,
sogsRoomInfo: initialSogsRoomInfoState,
settings: getSettingsInitialState(),
};
return createStore(initialState);
@ -91,29 +92,18 @@ function createSessionInboxStore() {
function setupLeftPane(forceUpdateInboxComponent: () => void) {
window.openConversationWithMessages = openConversationWithMessages;
window.inboxStore = createSessionInboxStore();
window.inboxStore.dispatch(updateAllOnStorageReady());
forceUpdateInboxComponent();
}
const SomeDeviceOutdatedSyncingNotice = () => {
const forceUpdate = useUpdate();
const isShown = Boolean(window.getSettingValue(SettingsKey.someDeviceOutdatedSyncing));
// it would be nice to get the settings into a redux slice in addition to their Storage location and keep them in sync.
// So we could just use a selector here.
useInterval(() => {
const shouldBeShown = Storage.get(SettingsKey.someDeviceOutdatedSyncing);
if (!isShown && shouldBeShown) {
forceUpdate();
}
}, 1000);
const outdatedBannerShouldBeShown = useHasDeviceOutdatedSyncing();
const dismiss = async () => {
await window.setSettingValue(SettingsKey.someDeviceOutdatedSyncing, false);
forceUpdate();
await Storage.put(SettingsKey.someDeviceOutdatedSyncing, false);
};
if (!isShown) {
if (!outdatedBannerShouldBeShown) {
return null;
}
return (

View file

@ -2,7 +2,8 @@ import React from 'react';
import { TypingAnimation } from './TypingAnimation';
import styled from 'styled-components';
import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { useSelectedIsGroup } from '../../state/selectors/selectedConversation';
interface TypingBubbleProps {
conversationType: ConversationTypeEnum;
@ -22,11 +23,8 @@ const TypingBubbleContainer = styled.div<TypingBubbleProps>`
`;
export const TypingBubble = (props: TypingBubbleProps) => {
if (isOpenOrClosedGroup(props.conversationType)) {
return null;
}
if (!props.isTyping) {
const isOpenOrClosedGroup = useSelectedIsGroup();
if (!isOpenOrClosedGroup || !props.isTyping) {
return null;
}

View file

@ -56,6 +56,7 @@ import {
getSelectedConversation,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key';
export interface ReplyingToMessageProps {
convoId: string;
@ -601,7 +602,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
private renderStagedLinkPreview(): JSX.Element | null {
// Don't generate link previews if user has turned them off
if (!(window.getSettingValue('link-preview-setting') || false)) {
if (!(window.getSettingValue(SettingsKey.settingsLinkPreview) || false)) {
return null;
}

View file

@ -8,9 +8,9 @@ import styled, { css } from 'styled-components';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
import {
getMessageContentSelectorProps,
getMessageTextProps,
getQuotedMessageToAnimate,
getShouldHighlightMessage,
useMessageIsDeleted,
} from '../../../../state/selectors/conversations';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment';
@ -96,6 +96,7 @@ export const MessageContent = (props: Props) => {
const contentProps = useSelector(state =>
getMessageContentSelectorProps(state as any, props.messageId)
);
const isDeleted = useMessageIsDeleted(props.messageId);
const [isMessageVisible, setMessageIsVisible] = useState(false);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
@ -149,13 +150,6 @@ export const MessageContent = (props: Props) => {
const { direction, text, timestamp, serverTimestamp, previews } = contentProps;
const selectedMsg = useSelector(state => getMessageTextProps(state as any, props.messageId));
let isDeleted = false;
if (selectedMsg && selectedMsg.isDeleted !== undefined) {
isDeleted = selectedMsg.isDeleted;
}
const hasContentAfterAttachmentAndQuote = !isEmpty(previews) || !isEmpty(text);
const toolTipTitle = moment(serverTimestamp || timestamp).format('llll');

View file

@ -9,6 +9,7 @@ import {
} from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon';
import { MessageBody } from './MessageBody';
import { StateType } from '../../../../state/reducer';
type Props = {
messageId: string;
@ -20,7 +21,7 @@ export type MessageTextSelectorProps = Pick<
>;
export const MessageText = (props: Props) => {
const selected = useSelector(state => getMessageTextProps(state as any, props.messageId));
const selected = useSelector((state: StateType) => getMessageTextProps(state, props.messageId));
const multiSelectMode = useSelector(isMessageSelectionMode);
if (!selected) {

View file

@ -52,7 +52,7 @@ const ChangeItemLeft = (left: Array<string>): string => {
// tslint:disable-next-line: cyclomatic-complexity
const ChangeItem = (change: PropsForGroupUpdateType): string => {
const type = change.type;
const { type } = change;
switch (type) {
case 'name':
return window.i18n('titleIsNow', [change.newName || '']);

View file

@ -24,6 +24,7 @@ import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIconButton } from '../icon';
import { ConfigurationDumpSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncDumpJob';
const handleSaveQRCode = (event: MouseEvent) => {
event.preventDefault();
@ -360,6 +361,7 @@ async function commitProfileEdits(newName: string, scaledAvatarUrl: string | nul
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
await setLastProfileUpdateTimestamp(Date.now());
} else {
await setLastProfileUpdateTimestamp(Date.now());

View file

@ -3,11 +3,7 @@ import { getConversationController } from '../../session/conversations';
import { syncConfigurationIfNeeded } from '../../session/utils/sync/syncUtils';
import { useDispatch, useSelector } from 'react-redux';
import {
Data,
hasSyncedInitialConfigurationItem,
lastAvatarUploadTimestamp,
} from '../../data/data';
import { Data } from '../../data/data';
import { getMessageQueue } from '../../session/sending';
// tslint:disable: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
@ -47,6 +43,7 @@ import { forceRefreshRandomSnodePool } from '../../session/apis/snode_api/snodeP
import { isDarkTheme } from '../../state/selectors/theme';
import { ThemeStateType } from '../../themes/constants/colors';
import { switchThemeTo } from '../../themes/switchTheme';
import { SettingsKey } from '../../data/settings-key';
const Section = (props: { type: SectionType }) => {
const ourNumber = useSelector(getOurNumber);
@ -172,14 +169,15 @@ const triggerSyncIfNeeded = async () => {
.get(us)
.setIsApproved(true, true);
const didWeHandleAConfigurationMessageAlready =
(await Data.getItemById(hasSyncedInitialConfigurationItem))?.value || false;
(await Data.getItemById(SettingsKey.hasSyncedInitialConfigurationItem))?.value || false;
if (didWeHandleAConfigurationMessageAlready) {
await syncConfigurationIfNeeded();
}
};
const triggerAvatarReUploadIfNeeded = async () => {
const lastTimeStampAvatarUpload = (await Data.getItemById(lastAvatarUploadTimestamp))?.value || 0;
const lastTimeStampAvatarUpload =
(await Data.getItemById(SettingsKey.lastAvatarUploadTimestamp))?.value || 0;
if (Date.now() - lastTimeStampAvatarUpload > DURATION.DAYS * 14) {
window.log.info('Reuploading avatar...');

View file

@ -36,7 +36,7 @@ export const LightboxGallery = (props: Props) => {
const selectedConversation = useSelectedConversationKey();
if (!selectedConversation) {
throw new Error('LightboxGallery: selectedConversation is undefined');
return null;
}
const dispatch = useDispatch();

View file

@ -16,6 +16,7 @@ import {
} from '../../util/accountManager';
import { fromHex } from '../../session/utils/String';
import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage';
import { SettingsKey } from '../../data/settings-key';
// tslint:disable: use-simple-attributes
@ -59,11 +60,7 @@ export async function signUp(signUpDetails: {
try {
await resetRegistration();
await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName);
await Data.createOrUpdateItem({
id: 'hasSyncedInitialConfigurationItem',
value: true,
timestamp: Date.now(),
});
await Storage.put(SettingsKey.hasSyncedInitialConfigurationItem, Date.now());
await setSignWithRecoveryPhrase(false);
trigger('openInbox');
} catch (e) {

View file

@ -1,7 +1,6 @@
import React from 'react';
// tslint:disable-next-line: no-submodule-imports
import useUpdate from 'react-use/lib/useUpdate';
import { Data, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data';
import { SettingsKey } from '../../../data/settings-key';
import { ConversationTypeEnum } from '../../../models/conversationAttributes';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
@ -11,9 +10,10 @@ import { TypingBubble } from '../../conversation/TypingBubble';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
import { displayPasswordModal } from '../SessionSettings';
import { Storage } from '../../../util/storage';
import { useHasLinkPreviewEnabled } from '../../../state/selectors/settings';
async function toggleLinkPreviews(forceUpdate: () => void) {
const isToggleOn = Boolean(window.getSettingValue(SettingsKey.settingsLinkPreview));
async function toggleLinkPreviews(isToggleOn: boolean, forceUpdate: () => void) {
if (!isToggleOn) {
window.inboxStore?.dispatch(
updateConfirmModal({
@ -29,7 +29,7 @@ async function toggleLinkPreviews(forceUpdate: () => void) {
);
} else {
await window.setSettingValue(SettingsKey.settingsLinkPreview, false);
await Data.createOrUpdateItem({ id: hasLinkPreviewPopupBeenDisplayed, value: false });
await Storage.put(SettingsKey.hasLinkPreviewPopupBeenDisplayed, false);
forceUpdate();
}
}
@ -48,7 +48,7 @@ export const SettingsCategoryPrivacy = (props: {
onPasswordUpdated: (action: string) => void;
}) => {
const forceUpdate = useUpdate();
const isLinkPreviewsOn = Boolean(window.getSettingValue(SettingsKey.settingsLinkPreview));
const isLinkPreviewsOn = useHasLinkPreviewEnabled();
if (props.hasPassword !== null) {
return (
@ -76,7 +76,7 @@ export const SettingsCategoryPrivacy = (props: {
/>
<SessionToggleWithDescription
onClickToggle={async () => {
await toggleLinkPreviews(forceUpdate);
await toggleLinkPreviews(isLinkPreviewsOn, forceUpdate);
}}
title={window.i18n('linkPreviewsTitle')}
description={window.i18n('linkPreviewDescription')}

View file

@ -53,10 +53,6 @@ export type SwarmNode = Snode & {
address: string;
};
export const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
export const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
export const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
// Basic
async function shutdown(): Promise<void> {
// Stop accepting new SQL jobs, flush outstanding queue
@ -654,7 +650,7 @@ async function getSnodePoolFromDb(): Promise<Array<Snode> | null> {
}
async function updateSnodePoolOnDb(snodesAsJsonString: string): Promise<void> {
await Data.createOrUpdateItem({ id: SNODE_POOL_ITEM_ID, value: snodesAsJsonString });
await Storage.put(SNODE_POOL_ITEM_ID, snodesAsJsonString);
}
function keysToArrayBuffer(keys: any, data: any) {
@ -692,8 +688,8 @@ const ITEM_KEYS: Object = {
};
/**
* Note: In the app, you should always call createOrUpdateItem through Data.createOrUpdateItem (from the data.ts file).
* This is to ensure testing and stubbbing works as expected
* For anything related to the UI and redux, do not use `createOrUpdateItem` directly. Instead use Storage.put (from the utils folder).
* `Storage.put` will update the settings redux slice if needed but createOrUpdateItem will not.
*/
export async function createOrUpdateItem(data: StorageItem): Promise<void> {
const { id } = data;

View file

@ -9,7 +9,10 @@ const settingsStartInTray = 'start-in-tray-setting';
const settingsOpengroupPruning = 'prune-setting';
const settingsNotification = 'notification-setting';
const settingsAudioNotification = 'audio-notification-setting';
const someDeviceOutdatedSyncing = 'some-device-outdated-syncing';
const someDeviceOutdatedSyncing = 'someDeviceOutdatedSyncing';
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
export const SettingsKey = {
settingsReadReceipt,
@ -23,7 +26,10 @@ export const SettingsKey = {
settingsNotification,
settingsAudioNotification,
someDeviceOutdatedSyncing,
};
hasSyncedInitialConfigurationItem,
lastAvatarUploadTimestamp,
hasLinkPreviewPopupBeenDisplayed,
} as const;
export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';
export const SNODE_POOL_ITEM_ID = 'SNODE_POOL_ITEM_ID';

View file

@ -6,7 +6,7 @@ import { CallManager, SyncUtils, ToastUtils, UserUtils } from '../session/utils'
import { SessionButtonColor } from '../components/basic/SessionButton';
import { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings';
import { Data, hasLinkPreviewPopupBeenDisplayed, lastAvatarUploadTimestamp } from '../data/data';
import { Data } from '../data/data';
import { uploadFileToFsWithOnionV4 } from '../session/apis/file_server_api/FileServerApi';
import { getConversationController } from '../session/conversations';
import { getSodiumRenderer } from '../session/crypto';
@ -37,11 +37,13 @@ import { processNewAttachment } from '../types/MessageAttachment';
import { IMAGE_JPEG } from '../types/MIME';
import { BlockedNumberController } from '../util/blockedNumberController';
import { encryptProfile } from '../util/crypto/profileEncrypter';
import { setLastProfileUpdateTimestamp } from '../util/storage';
import { Storage, setLastProfileUpdateTimestamp } from '../util/storage';
import { OpenGroupUtils } from '../session/apis/open_group_api/utils';
import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups';
import { leaveClosedGroup } from '../session/group/closed-group';
import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
import { SettingsKey } from '../data/settings-key';
import { ConfigurationDumpSync } from '../session/utils/job_runners/jobs/ConfigurationSyncDumpJob';
export function copyPublicKeyByConvoId(convoId: string) {
if (OpenGroupUtils.isOpenGroupV2(convoId)) {
@ -457,12 +459,13 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
avatarImageId: fileId,
});
const newTimestampReupload = Date.now();
await Data.createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload });
await Storage.put(SettingsKey.lastAvatarUploadTimestamp, newTimestampReupload);
if (newAvatarDecrypted) {
await setLastProfileUpdateTimestamp(Date.now());
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
} else {
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
@ -502,22 +505,22 @@ export async function replyToMessage(messageId: string) {
*/
export async function showLinkSharingConfirmationModalDialog(e: any) {
const pastedText = e.clipboardData.getData('text');
if (isURL(pastedText) && !window.getSettingValue('link-preview-setting', false)) {
if (isURL(pastedText) && !window.getSettingValue(SettingsKey.settingsLinkPreview, false)) {
const alreadyDisplayedPopup =
(await Data.getItemById(hasLinkPreviewPopupBeenDisplayed))?.value || false;
(await Data.getItemById(SettingsKey.hasLinkPreviewPopupBeenDisplayed))?.value || false;
if (!alreadyDisplayedPopup) {
window.inboxStore?.dispatch(
updateConfirmModal({
shouldShowConfirm:
!window.getSettingValue('link-preview-setting') && !alreadyDisplayedPopup,
!window.getSettingValue(SettingsKey.settingsLinkPreview) && !alreadyDisplayedPopup,
title: window.i18n('linkPreviewsTitle'),
message: window.i18n('linkPreviewsConfirmMessage'),
okTheme: SessionButtonColor.Danger,
onClickOk: async () => {
await window.setSettingValue('link-preview-setting', true);
await window.setSettingValue(SettingsKey.settingsLinkPreview, true);
},
onClickClose: async () => {
await Data.createOrUpdateItem({ id: hasLinkPreviewPopupBeenDisplayed, value: true });
await Storage.put(SettingsKey.hasLinkPreviewPopupBeenDisplayed, true);
},
})
);

View file

@ -24,6 +24,7 @@ import { initialiseEmojiData } from '../util/emoji';
import { switchPrimaryColorTo } from '../themes/switchPrimaryColor';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { runners } from '../session/utils/job_runners/JobRunner';
import { SettingsKey } from '../data/settings-key';
// tslint:disable: max-classes-per-file
// Globally disable drag and drop
@ -163,7 +164,7 @@ Storage.onready(async () => {
// Stop background processing
AttachmentDownloads.stop();
// Stop processing incoming messages
// FIXME audric stop polling opengroupv2 and swarm nodes
// TODO stop polling opengroupv2 and swarm nodes
// Shut down the data interface cleanly
await Data.shutdown();
@ -262,11 +263,6 @@ async function start() {
WhisperEvents.on('registration_done', async () => {
window.log.info('handling registration event');
// Disable link previews as default per Kee
Storage.onready(async () => {
await Storage.put('link-preview-setting', false);
});
await connect();
});
@ -287,7 +283,7 @@ async function start() {
});
}
function openStandAlone() {
function showRegistrationView() {
ReactDOM.render(<SessionRegistrationView />, document.getElementById('root'));
}
ExpirationTimerOptions.initExpiringMessageListener();
@ -298,7 +294,7 @@ async function start() {
} else {
const primaryColor = window.Events.getPrimaryColorSetting();
await switchPrimaryColorTo(primaryColor);
openStandAlone();
showRegistrationView();
}
window.addEventListener('focus', () => {
@ -386,7 +382,7 @@ async function start() {
if (launchCount === 1) {
// Initialise default settings
await window.setSettingValue('hide-menu-bar', true);
await window.setSettingValue('link-preview-setting', false);
await window.setSettingValue(SettingsKey.settingsLinkPreview, false);
}
WhisperEvents.on('openInbox', () => {

View file

@ -1219,7 +1219,7 @@ function insertContactIntoContactWrapper(
const dbApproved = !!contact.isApproved || false;
const dbApprovedMe = !!contact.didApproveMe || false;
const dbBlocked = blockedNumbers.includes(contact.id);
const priority = contact.priority || 0;
const priority = contact.priority || CONVERSATION_PRIORITIES.default;
const expirationTimerSeconds = contact.expireTimer || 0;
const wrapperContact = getContactInfoFromDBValues({
@ -1581,12 +1581,7 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
ourDbProfileKey
);
} else {
userProfileWrapper.setUserInfo(
ourDbName,
ourConvoPriority, // consider that the Note to self is hidden on a fresh account (without avatar set)
'',
new Uint8Array()
);
userProfileWrapper.setUserInfo(ourDbName, ourConvoPriority, '', new Uint8Array());
}
insertContactIntoContactWrapper(

View file

@ -1,5 +1,4 @@
export type StorageItem = {
id: string;
value: any;
timestamp?: number;
};

View file

@ -1,6 +1,6 @@
import { compact, isEmpty, toNumber } from 'lodash';
import { compact, isEmpty, isNumber, toNumber } from 'lodash';
import { ConfigDumpData } from '../data/configDump/configDump';
import { Data, hasSyncedInitialConfigurationItem } from '../data/data';
import { Data } from '../data/data';
import { ConversationInteraction } from '../interactions';
import { ConversationTypeEnum } from '../models/conversationAttributes';
import { SignalService } from '../protobuf';
@ -27,7 +27,11 @@ import { configurationMessageReceived, trigger } from '../shims/events';
import { assertUnreachable } from '../types/sqlSharedTypes';
import { BlockedNumberController } from '../util';
import { Registration } from '../util/registration';
import { getLastProfileUpdateTimestamp, setLastProfileUpdateTimestamp } from '../util/storage';
import {
Storage,
getLastProfileUpdateTimestamp,
setLastProfileUpdateTimestamp,
} from '../util/storage';
import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions';
import {
ContactsWrapperActions,
@ -703,8 +707,19 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
return;
}
const envelopeTimestamp = toNumber(envelope.timestamp);
const lastConfigUpdate = await Data.getItemById(hasSyncedInitialConfigurationItem);
const lastConfigTimestamp = lastConfigUpdate?.timestamp;
// at some point, we made the hasSyncedInitialConfigurationItem item to have a value=true and a timestamp set.
// we can actually just use the timestamp as a boolean, as if it is set, we know we have synced the initial config
// but we still need to handle the case where the timestamp was set when the value is true (for backwards compatiblity, until we get rid of the config message legacy)
const lastConfigUpdate = await Data.getItemById(SettingsKey.hasSyncedInitialConfigurationItem);
let lastConfigTimestamp: number | undefined;
if (isNumber(lastConfigUpdate?.value)) {
lastConfigTimestamp = lastConfigUpdate?.value;
} else if (isNumber((lastConfigUpdate as any)?.timestamp)) {
lastConfigTimestamp = (lastConfigUpdate as any)?.timestamp; // ugly, but we can remove it once we dropped support for legacy config message, see comment above
}
const isNewerConfig =
!lastConfigTimestamp || (lastConfigTimestamp && lastConfigTimestamp < envelopeTimestamp);
@ -713,11 +728,7 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
return;
}
await Data.createOrUpdateItem({
id: 'hasSyncedInitialConfigurationItem',
value: true,
timestamp: envelopeTimestamp,
});
await Storage.put(SettingsKey.hasSyncedInitialConfigurationItem, envelopeTimestamp);
// we only want to apply changes to closed groups if we never got them
// new opengroups get added when we get a new closed group message from someone, or a sync'ed message from outself creating the group

View file

@ -13,6 +13,7 @@ import { SogsBlinding } from './sogsBlinding';
import { fromHexToArray } from '../../../utils/String';
import { KNOWN_BLINDED_KEYS_ITEM } from '../../../../data/settings-key';
import { roomHasBlindEnabled } from '../../../../types/sqlSharedTypes';
import { Storage } from '../../../../util/storage';
export type BlindedIdMapping = {
blindedId: string;
@ -65,10 +66,7 @@ export async function loadKnownBlindedKeys() {
*/
export async function writeKnownBlindedKeys() {
if (cachedKnownMapping && cachedKnownMapping.length) {
await Data.createOrUpdateItem({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(cachedKnownMapping),
});
await Storage.put(KNOWN_BLINDED_KEYS_ITEM, JSON.stringify(cachedKnownMapping));
}
}

View file

@ -1,9 +1,13 @@
import { isNumber } from 'lodash';
import { Data } from '../../../data/data';
import { Storage } from '../../../util/storage';
let hasSeenHardfork190: boolean | undefined;
let hasSeenHardfork191: boolean | undefined;
const hasSeenHardfork190ItemId = 'hasSeenHardfork190';
const hasSeenHardfork191ItemId = 'hasSeenHardfork191';
/**
* this is only intended for testing. Do not call this in production.
*/
@ -11,13 +15,16 @@ export function resetHardForkCachedValues() {
hasSeenHardfork190 = hasSeenHardfork191 = undefined;
}
/**
* Not used anymore, but keeping those here in case we ever need to do hardfork enabling of features again
*/
export async function getHasSeenHF190() {
if (hasSeenHardfork190 === undefined) {
// read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork190 = (await Data.getItemById('hasSeenHardfork190'))?.value;
const oldHhasSeenHardfork190 = (await Data.getItemById(hasSeenHardfork190ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork190 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: false });
await Storage.put(hasSeenHardfork190ItemId, false);
hasSeenHardfork190 = false;
} else {
hasSeenHardfork190 = oldHhasSeenHardfork190;
@ -26,14 +33,17 @@ export async function getHasSeenHF190() {
return hasSeenHardfork190;
}
/**
* Not used anymore, but keeping those here in case we ever need to do hardfork enabling of features again
*/
export async function getHasSeenHF191() {
if (hasSeenHardfork191 === undefined) {
// read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork191 = (await Data.getItemById('hasSeenHardfork191'))?.value;
const oldHhasSeenHardfork191 = (await Data.getItemById(hasSeenHardfork191ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork191 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: false });
await Storage.put(hasSeenHardfork191ItemId, false);
hasSeenHardfork191 = false;
} else {
hasSeenHardfork191 = oldHhasSeenHardfork191;
@ -45,18 +55,18 @@ export async function getHasSeenHF191() {
export async function handleHardforkResult(json: Record<string, any>) {
if (hasSeenHardfork190 === undefined || hasSeenHardfork191 === undefined) {
// read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork190 = (await Data.getItemById('hasSeenHardfork190'))?.value;
const oldHasSeenHardfork191 = (await Data.getItemById('hasSeenHardfork191'))?.value;
const oldHhasSeenHardfork190 = (await Data.getItemById(hasSeenHardfork190ItemId))?.value;
const oldHasSeenHardfork191 = (await Data.getItemById(hasSeenHardfork191ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork190 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: false });
await Storage.put(hasSeenHardfork190ItemId, false);
hasSeenHardfork190 = false;
} else {
hasSeenHardfork190 = oldHhasSeenHardfork190;
}
if (oldHasSeenHardfork191 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: false });
await Storage.put(hasSeenHardfork191ItemId, false);
hasSeenHardfork191 = false;
} else {
hasSeenHardfork191 = oldHasSeenHardfork191;
@ -78,13 +88,11 @@ export async function handleHardforkResult(json: Record<string, any>) {
isNumber(json.hf[1])
) {
if (!hasSeenHardfork190 && json.hf[0] >= 19 && json.hf[1] >= 0) {
// window.log.info('[HF]: We just detected HF 19.0 on "retrieve"');
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: true });
await Storage.put(hasSeenHardfork190ItemId, true);
hasSeenHardfork190 = true;
}
if (!hasSeenHardfork191 && json.hf[0] >= 19 && json.hf[1] >= 1) {
// window.log.info('[HF]: We just detected HF 19.1 on "retrieve"');
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: true });
await Storage.put(hasSeenHardfork191ItemId, true);
hasSeenHardfork191 = true;
}
}

View file

@ -29,7 +29,7 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
// FIXME we shouldn't need this once android recieving refactor is done.
// TODO we shouldn't need this once android recieving refactor is done.
// the envelope stores the groupId for a closed group already.
if (this.groupId) {
const groupMessage = new SignalService.GroupContext();

View file

@ -5,6 +5,7 @@ import { ContentMessage } from '../messages/outgoing';
import { PubKey } from '../types';
import { MessageUtils } from '../utils';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { Storage } from '../../util/storage';
// This is an abstraction for storing pending messages.
// Ideally we want to store pending messages in the database so that
@ -140,9 +141,6 @@ export class PendingMessageCache {
});
const encodedPendingMessages = JSON.stringify(encodedCache) || '[]';
await Data.createOrUpdateItem({
id: 'pendingMessages',
value: encodedPendingMessages,
});
await Storage.put('pendingMessages', encodedPendingMessages);
}
}

View file

@ -11,6 +11,7 @@ import {
RunJobResult,
TypeOfPersistedData,
} from './PersistedJob';
import { Storage } from '../../../util/storage';
/**
* 'job_in_progress' if there is already a job in progress
@ -177,10 +178,7 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
private async writeJobsToDB() {
const serialized = this.getSerializedJobs();
window.log.debug(`writing to db for "${this.jobRunnerType}": `, serialized);
await Data.createOrUpdateItem({
id: this.getJobRunnerItemId(),
value: JSON.stringify(serialized),
});
await Storage.put(this.getJobRunnerItemId(), JSON.stringify(serialized));
}
private async addJobUnchecked(job: PersistedJob<T>) {

View file

@ -31,6 +31,7 @@ import { ConfigurationDumpSync } from '../job_runners/jobs/ConfigurationSyncDump
import { ConfigurationSync } from '../job_runners/jobs/ConfigurationSyncJob';
import { fromBase64ToArray, fromHexToArray } from '../String';
import { getCompleteUrlFromRoom } from '../../apis/open_group_api/utils/OpenGroupUtils';
import { Storage } from '../../../util/storage';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -38,7 +39,7 @@ const getLastSyncTimestampFromDb = async (): Promise<number | undefined> =>
(await Data.getItemById(ITEM_ID_LAST_SYNC_TIMESTAMP))?.value;
const writeLastSyncTimestampToDb = async (timestamp: number) =>
Data.createOrUpdateItem({ id: ITEM_ID_LAST_SYNC_TIMESTAMP, value: timestamp });
Storage.put(ITEM_ID_LAST_SYNC_TIMESTAMP, timestamp);
/**
* Conditionally Syncs user configuration with other devices linked.
@ -73,6 +74,7 @@ export const syncConfigurationIfNeeded = async () => {
} else {
await ConfigurationDumpSync.queueNewJobIfNeeded();
await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
}
};

View file

@ -0,0 +1,73 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { SettingsKey } from '../../data/settings-key';
import { isBoolean } from 'lodash';
import { Storage } from '../../util/storage';
const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
] as const;
export type SettingsState = {
settingsBools: Record<typeof SettingsBoolsKeyTrackedInRedux[number], boolean>;
};
export function getSettingsInitialState() {
return {
settingsBools: {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
},
};
}
function isTrackedBoolean(key: string): key is typeof SettingsBoolsKeyTrackedInRedux[number] {
return SettingsBoolsKeyTrackedInRedux.indexOf(key as any) !== -1;
}
/**
* This slice is the one holding the settings of the currently logged in user in redux.
* This is in addition to the settings stored in the Storage class but is a memory only representation of them.
* You should not try to make changes here, but instead through the Storage class.
* What you can do with this slice, is to create selectors and hooks to keep your UI in sync with the state in whatever is Storage.
*/
const settingsSlice = createSlice({
name: 'settings',
// when this createSlice gets invoke, the storage is not ready, but redux still wants a state so we just avoid hitting the storage.
// Once the storage is ready,
initialState: getSettingsInitialState(),
reducers: {
updateAllOnStorageReady(state) {
const linkPreview = Storage.get(SettingsKey.settingsLinkPreview, false);
const outdatedSync = Storage.get(SettingsKey.someDeviceOutdatedSyncing, false);
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
? outdatedSync
: false;
state.settingsBools['link-preview-setting'] = isBoolean(linkPreview) ? linkPreview : false; // this is the value of SettingsKey.settingsLinkPreview
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {
const { id, value } = action.payload;
if (!isTrackedBoolean(id) || !isBoolean(value)) return state;
state.settingsBools[id] = value;
return state;
},
deleteSettingsBoolValue(state, action: PayloadAction<string>) {
if (!isTrackedBoolean(action.payload)) return state;
delete state.settingsBools[action.payload];
return state;
},
},
});
const { actions, reducer } = settingsSlice;
export const {
updateSettingsBoolValue,
deleteSettingsBoolValue,
updateAllOnStorageReady,
} = actions;
export const settingsReducer = reducer;

View file

@ -19,6 +19,7 @@ import {
StagedAttachmentsStateType,
} from './ducks/stagedAttachments';
import { PrimaryColorStateType, ThemeStateType } from '../themes/constants/colors';
import { settingsReducer, SettingsState } from './ducks/settings';
export type StateType = {
search: SearchStateType;
@ -35,6 +36,7 @@ export type StateType = {
stagedAttachments: StagedAttachmentsStateType;
call: CallStateType;
sogsRoomInfo: SogsRoomInfoState;
settings: SettingsState;
};
export const reducers = {
@ -52,6 +54,7 @@ export const reducers = {
stagedAttachments,
call,
sogsRoomInfo: ReduxSogsRoomInfos.sogsRoomInfoReducer,
settings: settingsReducer,
};
// Making this work would require that our reducer signature supported AnyAction, not

View file

@ -42,6 +42,7 @@ import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash';
import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions';
import { getModeratorsOutsideRedux } from './sogsRoomInfo';
import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation';
import { useSelector } from 'react-redux';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -882,6 +883,11 @@ export const getMessageTextProps = createSelector(getMessagePropsByMessageId, (p
return msgProps;
});
export const useMessageIsDeleted = (messageId: string): boolean => {
const props = useSelector((state: StateType) => getMessagePropsByMessageId(state, messageId));
return props?.propsForMessage.isDeleted || false;
};
export const getMessageContextMenuProps = createSelector(getMessagePropsByMessageId, (props):
| MessageContextMenuSelectorProps
| undefined => {

View file

@ -80,10 +80,10 @@ export function getSelectedCanWrite(state: StateType) {
if (!selectedConvo) {
return false;
}
const canWrite = getCanWrite(state, selectedConvoPubkey);
const canWriteSogs = getCanWrite(state, selectedConvoPubkey);
const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo;
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWrite));
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWriteSogs));
}
/**

View file

@ -0,0 +1,19 @@
import { useSelector } from 'react-redux';
import { SettingsKey } from '../../data/settings-key';
import { StateType } from '../reducer';
const getLinkPreviewEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.settingsLinkPreview];
const getHasDeviceOutdatedSyncing = (state: StateType) =>
state.settings.settingsBools[SettingsKey.someDeviceOutdatedSyncing];
export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
};
export const useHasDeviceOutdatedSyncing = () => {
const value = useSelector(getHasDeviceOutdatedSyncing);
return Boolean(value);
};

View file

@ -197,8 +197,10 @@ async function registrationDone(ourPubkey: string, displayName: string) {
await conversation.setIsApproved(true, false);
await conversation.setDidApproveMe(true, false);
// when onboarding, hide the note to self by default.
await conversation.setHidden(true);
await conversation.commit();
const user = {
ourNumber: getOurPubKeyStrFromCache(),
ourPrimary: ourPubkey,

View file

@ -1,6 +1,7 @@
import { Data } from '../data/data';
import { commitConversationAndRefreshWrapper } from '../models/conversation';
import { PubKey } from '../session/types';
import { Storage } from './storage';
const BLOCKED_NUMBERS_ID = 'blocked';
@ -112,9 +113,6 @@ export class BlockedNumberController {
}
private static async saveToDB(id: string, numbers: Set<string>): Promise<void> {
await Data.createOrUpdateItem({
id,
value: [...numbers],
});
await Storage.put(id, [...numbers]);
}
}

View file

@ -1,10 +1,12 @@
import { Data } from '../data/data';
import { SessionKeyPair } from '../receiver/keypairs';
import { DEFAULT_RECENT_REACTS } from '../session/constants';
import { deleteSettingsBoolValue, updateSettingsBoolValue } from '../state/ducks/settings';
import { isBoolean } from 'lodash';
let ready = false;
type ValueType = string | number | boolean | SessionKeyPair;
type ValueType = string | number | boolean | SessionKeyPair | Array<string>;
type InsertedValueType = { id: string; value: ValueType };
let items: Record<string, InsertedValueType>;
let callbacks: Array<() => void> = [];
@ -23,6 +25,10 @@ async function put(key: string, value: ValueType) {
items[key] = data;
await Data.createOrUpdateItem(data);
if (isBoolean(value)) {
window?.inboxStore?.dispatch(updateSettingsBoolValue({ id: key, value }));
}
}
function get(key: string, defaultValue?: ValueType) {
@ -45,6 +51,9 @@ async function remove(key: string) {
// tslint:disable-next-line: no-dynamic-delete
delete items[key];
window?.inboxStore?.dispatch(deleteSettingsBoolValue(key));
await Data.removeItemById(key);
}

View file

@ -90,7 +90,6 @@ function initUserWrapper(
const userType = assertUserWrapperType(wrapperType);
const wrapper = getUserWrapper(wrapperType);
console.warn('initUserWrapper: ', wrapperType, options);
if (wrapper) {
throw new Error(`${wrapperType} already init`);
}