From 798533a41757e58ea20bb259cd33794c52f6a563 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Mon, 30 Aug 2021 14:39:57 -0700 Subject: [PATCH] Keep UI settings on heartbeat expiration --- ts/SignalProtocolStore.ts | 5 +++-- ts/background.ts | 11 ++++++---- ts/sql/Client.ts | 5 +++-- ts/sql/Interface.ts | 3 ++- ts/sql/Server.ts | 33 +++++++++++++++++++++++++++--- ts/types/RemoveAllConfiguration.ts | 7 +++++++ ts/types/Storage.d.ts | 2 ++ ts/types/StorageUIKeys.ts | 30 +++++++++++++++++++++++++++ 8 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 ts/types/RemoveAllConfiguration.ts create mode 100644 ts/types/StorageUIKeys.ts diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index afcaa6d5c..b96f9281d 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -45,6 +45,7 @@ import { UnprocessedUpdateType, } from './textsecure/Types.d'; import { getSendOptions } from './util/getSendOptions'; +import type { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds @@ -1949,8 +1950,8 @@ export class SignalProtocolStore extends EventsMixin { await window.ConversationController.load(); } - async removeAllConfiguration(): Promise { - await window.Signal.Data.removeAllConfiguration(); + async removeAllConfiguration(mode: RemoveAllConfiguration): Promise { + await window.Signal.Data.removeAllConfiguration(mode); await this.hydrateCaches(); window.storage.reset(); diff --git a/ts/background.ts b/ts/background.ts index ba48bf165..d0025d0e1 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -94,6 +94,7 @@ import { SignalService as Proto } from './protobuf'; import { onRetryRequest, onDecryptionError } from './util/handleRetry'; import { themeChanged } from './shims/themeChanged'; import { createIPCEvents } from './util/createIPCEvents'; +import { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000; @@ -658,7 +659,7 @@ export async function startApp(): Promise { window.log.warn( `This instance has not been used for 30 days. Last heartbeat: ${lastHeartbeat}. Last startup: ${previousLastStartup}.` ); - await unlinkAndDisconnect(); + await unlinkAndDisconnect(RemoveAllConfiguration.Soft); } // Start heartbeat timer @@ -3336,7 +3337,9 @@ export async function startApp(): Promise { return false; } - async function unlinkAndDisconnect() { + async function unlinkAndDisconnect( + mode: RemoveAllConfiguration + ): Promise { window.Whisper.events.trigger('unauthorized'); if (messageReceiver) { @@ -3369,7 +3372,7 @@ export async function startApp(): Promise { ); try { - await window.textsecure.storage.protocol.removeAllConfiguration(); + await window.textsecure.storage.protocol.removeAllConfiguration(mode); // This was already done in the database with removeAllConfiguration; this does it // for all the conversation models in memory. @@ -3423,7 +3426,7 @@ export async function startApp(): Promise { error.name === 'HTTPError' && (error.code === 401 || error.code === 403) ) { - unlinkAndDisconnect(); + unlinkAndDisconnect(RemoveAllConfiguration.Full); return; } diff --git a/ts/sql/Client.ts b/ts/sql/Client.ts index 9a307e5b7..f29aaec69 100644 --- a/ts/sql/Client.ts +++ b/ts/sql/Client.ts @@ -33,6 +33,7 @@ import { cleanDataForIpc } from './cleanDataForIpc'; import { ReactionType } from '../types/Reactions'; import { ConversationColorType, CustomColorType } from '../types/Colors'; import type { ProcessGroupCallRingRequestResult } from '../types/Calling'; +import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration'; import { ConversationModelCollectionType, @@ -1527,8 +1528,8 @@ async function removeAll() { await channels.removeAll(); } -async function removeAllConfiguration() { - await channels.removeAllConfiguration(); +async function removeAllConfiguration(type?: RemoveAllConfiguration) { + await channels.removeAllConfiguration(type); } async function cleanupOrphanedAttachments() { diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 34cfe572b..2d90ec69b 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -19,6 +19,7 @@ import type { ProcessGroupCallRingRequestResult } from '../types/Calling'; import { StorageAccessType } from '../types/Storage.d'; import type { AttachmentType } from '../types/Attachment'; import { BodyRangesType } from '../types/Util'; +import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration'; export type AttachmentDownloadJobTypeType = | 'long-message' @@ -416,7 +417,7 @@ export type DataInterface = { getRecentEmojis: (limit?: number) => Promise>; removeAll: () => Promise; - removeAllConfiguration: () => Promise; + removeAllConfiguration: (type?: RemoveAllConfiguration) => Promise; getMessagesNeedingUpgrade: ( limit: number, diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index f4b5a8333..6eff3523a 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -32,17 +32,20 @@ import { import { ReadStatus } from '../messages/MessageReadStatus'; import { GroupV2MemberType } from '../model-types.d'; import { ReactionType } from '../types/Reactions'; +import { STORAGE_UI_KEYS } from '../types/StorageUIKeys'; import { StoredJob } from '../jobs/types'; import { assert } from '../util/assert'; import { combineNames } from '../util/combineNames'; import { dropNull } from '../util/dropNull'; import { isNormalNumber } from '../util/isNormalNumber'; import { isNotNil } from '../util/isNotNil'; +import { missingCaseError } from '../util/missingCaseError'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; import * as durations from '../util/durations'; import { formatCountForLogging } from '../logging/formatCountForLogging'; import { ConversationColorType, CustomColorType } from '../types/Colors'; import { ProcessGroupCallRingRequestResult } from '../types/Calling'; +import { RemoveAllConfiguration } from '../types/RemoveAllConfiguration'; import { AllItemsType, @@ -5427,22 +5430,46 @@ async function removeAll(): Promise { } // Anything that isn't user-visible data -async function removeAllConfiguration(): Promise { +async function removeAllConfiguration( + mode = RemoveAllConfiguration.Full +): Promise { const db = getInstance(); db.transaction(() => { db.exec( ` DELETE FROM identityKeys; - DELETE FROM items; DELETE FROM preKeys; DELETE FROM senderKeys; DELETE FROM sessions; DELETE FROM signedPreKeys; DELETE FROM unprocessed; DELETE FROM jobs; - ` + ` ); + + if (mode === RemoveAllConfiguration.Full) { + db.exec( + ` + DELETE FROM items; + ` + ); + } else if (mode === RemoveAllConfiguration.Soft) { + const itemIds: ReadonlyArray = db + .prepare('SELECT id FROM items') + .pluck(true) + .all(); + + const allowedSet = new Set(STORAGE_UI_KEYS); + for (const id of itemIds) { + if (!allowedSet.has(id)) { + removeById('items', id); + } + } + } else { + throw missingCaseError(mode); + } + db.exec( "UPDATE conversations SET json = json_remove(json, '$.senderKeyInfo');" ); diff --git a/ts/types/RemoveAllConfiguration.ts b/ts/types/RemoveAllConfiguration.ts new file mode 100644 index 000000000..0cfa62384 --- /dev/null +++ b/ts/types/RemoveAllConfiguration.ts @@ -0,0 +1,7 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export enum RemoveAllConfiguration { + Full = 'Full', + Soft = 'Soft', +} diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index 0d8a5f240..94cd2a39b 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -30,6 +30,8 @@ export type ThemeSettingType = 'system' | 'light' | 'dark'; export type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; +// This should be in sync with `UI_CONFIGURATION_KEYS` in +// `ts/textsecure/Storage.ts`. export type StorageAccessType = { 'always-relay-calls': boolean; 'audio-notification': boolean; diff --git a/ts/types/StorageUIKeys.ts b/ts/types/StorageUIKeys.ts new file mode 100644 index 000000000..ddf34ada9 --- /dev/null +++ b/ts/types/StorageUIKeys.ts @@ -0,0 +1,30 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { StorageAccessType } from './Storage.d'; + +// Configuration keys that only affect UI +export const STORAGE_UI_KEYS: ReadonlyArray = [ + 'always-relay-calls', + 'audio-notification', + 'auto-download-update', + 'badge-count-muted-conversations', + 'call-ringtone-notification', + 'call-system-notification', + 'hide-menu-bar', + 'system-tray-setting', + 'incoming-call-notification', + 'notification-draw-attention', + 'notification-setting', + 'spell-check', + 'theme-setting', + 'defaultConversationColor', + 'customColors', + 'showStickerPickerHint', + 'showStickersIntroduction', + 'preferred-video-input-device', + 'preferred-audio-input-device', + 'preferred-audio-output-device', + 'skinTone', + 'zoomFactor', +];