From 1cc478180ee71adfb366e59641207763488702e2 Mon Sep 17 00:00:00 2001 From: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:42:30 -0700 Subject: [PATCH] Fix call history deletion from sync messages --- ts/services/callHistoryLoader.ts | 1 + ts/sql/Interface.ts | 1 + ts/sql/Server.ts | 19 +++++++++++++ ts/state/ducks/callHistory.ts | 48 ++++++++++++++++++++++++++------ ts/util/callDisposition.ts | 19 ++++++++++++- ts/util/onCallLogEventSync.ts | 7 ++++- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/ts/services/callHistoryLoader.ts b/ts/services/callHistoryLoader.ts index d1a1533ba8..b31a8a7217 100644 --- a/ts/services/callHistoryLoader.ts +++ b/ts/services/callHistoryLoader.ts @@ -8,6 +8,7 @@ import { strictAssert } from '../util/assert'; let callsHistoryData: ReadonlyArray; export async function loadCallsHistory(): Promise { + await dataInterface.cleanupCallHistoryMessages(); callsHistoryData = await dataInterface.getAllCallHistory(); } diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 9fc9c5befa..3488f889a8 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -630,6 +630,7 @@ export type DataInterface = { }): Promise; getAllCallHistory: () => Promise>; clearCallHistory: (beforeTimestamp: number) => Promise>; + cleanupCallHistoryMessages: () => Promise; getCallHistoryUnreadCount(): Promise; markCallHistoryRead(callId: string): Promise; markAllCallHistoryRead(): Promise>; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 138a61721f..48e3517734 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -305,6 +305,7 @@ const dataInterface: ServerInterface = { getLastConversationMessage, getAllCallHistory, clearCallHistory, + cleanupCallHistoryMessages, getCallHistoryUnreadCount, markCallHistoryRead, markAllCallHistoryRead, @@ -3294,6 +3295,24 @@ async function clearCallHistory( })(); } +async function cleanupCallHistoryMessages(): Promise { + const db = getInstance(); + return db + .transaction(() => { + const [query, params] = sql` + DELETE FROM messages + WHERE messages.id IN ( + SELECT messages.id FROM messages + LEFT JOIN callsHistory ON callsHistory.callId IS messages.callId + WHERE messages.type IS 'call-history' + AND callsHistory.status IS ${CALL_STATUS_DELETED} + ) + `; + db.prepare(query).run(params); + }) + .immediate(); +} + async function getCallHistoryMessageByCallId(options: { conversationId: string; callId: string; diff --git a/ts/state/ducks/callHistory.ts b/ts/state/ducks/callHistory.ts index 99703b6be6..10d94b0257 100644 --- a/ts/state/ducks/callHistory.ts +++ b/ts/state/ducks/callHistory.ts @@ -3,6 +3,7 @@ import type { ReadonlyDeep } from 'type-fest'; import type { ThunkAction } from 'redux-thunk'; +import { omit } from 'lodash'; import type { StateType as RootStateType } from '../reducer'; import { clearCallHistoryDataAndSync } from '../../util/callDisposition'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; @@ -22,15 +23,21 @@ export type CallHistoryState = ReadonlyDeep<{ callHistoryByCallId: Record; }>; -const CALL_HISTORY_CACHE = 'callHistory/CACHE'; +const CALL_HISTORY_ADD = 'callHistory/ADD'; +const CALL_HISTORY_REMOVE = 'callHistory/REMOVE'; const CALL_HISTORY_RESET = 'callHistory/RESET'; const CALL_HISTORY_UPDATE_UNREAD = 'callHistory/UPDATE_UNREAD'; -export type CallHistoryCache = ReadonlyDeep<{ - type: typeof CALL_HISTORY_CACHE; +export type CallHistoryAdd = ReadonlyDeep<{ + type: typeof CALL_HISTORY_ADD; payload: CallHistoryDetails; }>; +export type CallHistoryRemove = ReadonlyDeep<{ + type: typeof CALL_HISTORY_REMOVE; + payload: CallHistoryDetails['callId']; +}>; + export type CallHistoryReset = ReadonlyDeep<{ type: typeof CALL_HISTORY_RESET; }>; @@ -41,7 +48,10 @@ export type CallHistoryUpdateUnread = ReadonlyDeep<{ }>; export type CallHistoryAction = ReadonlyDeep< - CallHistoryCache | CallHistoryReset | CallHistoryUpdateUnread + | CallHistoryAdd + | CallHistoryRemove + | CallHistoryReset + | CallHistoryUpdateUnread >; export function getEmptyState(): CallHistoryState { @@ -113,13 +123,26 @@ function markCallsTabViewed(): ThunkAction< }; } -function cacheCallHistory(callHistory: CallHistoryDetails): CallHistoryCache { +function addCallHistory(callHistory: CallHistoryDetails): CallHistoryAdd { return { - type: CALL_HISTORY_CACHE, + type: CALL_HISTORY_ADD, payload: callHistory, }; } +function removeCallHistory( + callId: CallHistoryDetails['callId'] +): CallHistoryRemove { + return { + type: CALL_HISTORY_REMOVE, + payload: callId, + }; +} + +function resetCallHistory(): CallHistoryReset { + return { type: CALL_HISTORY_RESET }; +} + function clearAllCallHistory(): ThunkAction< void, RootStateType, @@ -134,14 +157,16 @@ function clearAllCallHistory(): ThunkAction< log.error('Error clearing call history', Errors.toLogFormat(error)); } finally { // Just force a reset, even if the clear failed. - dispatch({ type: CALL_HISTORY_RESET }); + dispatch(resetCallHistory()); dispatch(updateCallHistoryUnreadCount()); } }; } export const actions = { - cacheCallHistory, + addCallHistory, + removeCallHistory, + resetCallHistory, clearAllCallHistory, updateCallHistoryUnreadCount, markCallHistoryRead, @@ -159,7 +184,7 @@ export function reducer( switch (action.type) { case CALL_HISTORY_RESET: return { ...state, edition: state.edition + 1, callHistoryByCallId: {} }; - case CALL_HISTORY_CACHE: + case CALL_HISTORY_ADD: return { ...state, callHistoryByCallId: { @@ -167,6 +192,11 @@ export function reducer( [action.payload.callId]: action.payload, }, }; + case CALL_HISTORY_REMOVE: + return { + ...state, + callHistoryByCallId: omit(state.callHistoryByCallId, action.payload), + }; case CALL_HISTORY_UPDATE_UNREAD: return { ...state, diff --git a/ts/util/callDisposition.ts b/ts/util/callDisposition.ts index 56e7e614f9..b751f6059e 100644 --- a/ts/util/callDisposition.ts +++ b/ts/util/callDisposition.ts @@ -785,8 +785,18 @@ async function updateLocalCallHistory( 'updateLocalCallHistory: Saving call history:', formatCallHistory(callHistory) ); + + const isDeleted = + callHistory.status === DirectCallStatus.Deleted || + callHistory.status === GroupCallStatus.Deleted; + await window.Signal.Data.saveCallHistory(callHistory); - window.reduxActions.callHistory.cacheCallHistory(callHistory); + + if (isDeleted) { + window.reduxActions.callHistory.removeCallHistory(callHistory.callId); + } else { + window.reduxActions.callHistory.addCallHistory(callHistory); + } const prevMessage = await window.Signal.Data.getCallHistoryMessageByCallId({ @@ -806,6 +816,13 @@ async function updateLocalCallHistory( ); } + if (isDeleted) { + if (prevMessage != null) { + await window.Signal.Data.removeMessage(prevMessage.id); + } + return callHistory; + } + let unread = false; if (callHistory.mode === CallMode.Direct) { unread = diff --git a/ts/util/onCallLogEventSync.ts b/ts/util/onCallLogEventSync.ts index 47911a6f71..a89d2a9b53 100644 --- a/ts/util/onCallLogEventSync.ts +++ b/ts/util/onCallLogEventSync.ts @@ -18,7 +18,12 @@ export async function onCallLogEventSync( if (event === CallLogEvent.Clear) { log.info(`onCallLogEventSync: Clearing call history before ${timestamp}`); - await window.Signal.Data.clearCallHistory(timestamp); + try { + await window.Signal.Data.clearCallHistory(timestamp); + } finally { + // We want to reset the call history even if the clear fails. + window.reduxActions.callHistory.resetCallHistory(); + } confirm(); } else { throw missingCaseError(event);