Update CallLogEvent to latest spec

This commit is contained in:
Jamie Kyle 2024-06-25 17:58:38 -07:00 committed by GitHub
parent 815fd77849
commit fc08e70c0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 366 additions and 142 deletions

View file

@ -604,7 +604,12 @@ message SyncMessage {
DELETE = 3;
}
/* Data identifying a conversation. The service ID for 1:1, the group ID for
* group, or the room ID for an ad-hoc call. See also
* `CallLogEvent/peerId`. */
optional bytes peerId = 1;
/* An identifier for a call. Generated directly for 1:1, or derived from
* the era ID for group and ad-hoc calls. See also `CallLogEvent/callId`. */
optional uint64 callId = 2;
optional uint64 timestamp = 3;
optional Type type = 4;
@ -627,10 +632,18 @@ message SyncMessage {
enum Type {
CLEAR = 0;
MARKED_AS_READ = 1;
MARKED_AS_READ_IN_CONVERSATION = 2;
}
optional Type type = 1;
optional uint64 timestamp = 2;
/* Data identifying a conversation. The service ID for 1:1, the group ID for
* group, or the room ID for an ad-hoc call. See also
* `CallEvent/peerId`. */
optional bytes peerId = 3;
/* An identifier for a call. Generated directly for 1:1, or derived from
* the era ID for group and ad-hoc calls. See also `CallEvent/callId`. */
optional uint64 callId = 4;
}
message DeleteForMe {
@ -641,7 +654,7 @@ message SyncMessage {
string threadE164 = 3;
}
}
message AddressableMessage {
oneof author {
string authorServiceId = 1;
@ -682,7 +695,7 @@ message SyncMessage {
repeated LocalOnlyConversationDelete localOnlyConversationDeletes = 3;
repeated AttachmentDelete attachmentDeletes = 4;
}
optional Sent sent = 1;
optional Contacts contacts = 2;
reserved /* groups */ 3;

View file

@ -28,6 +28,7 @@ import type {
CallHistoryFilter,
CallHistoryGroup,
CallHistoryPagination,
CallLogEventTarget,
} from '../types/CallDisposition';
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
@ -666,14 +667,17 @@ export type DataInterface = {
conversationId: string;
}): Promise<MessageType | undefined>;
getAllCallHistory: () => Promise<ReadonlyArray<CallHistoryDetails>>;
clearCallHistory: (
target: CallLogEventTarget
) => Promise<ReadonlyArray<string>>;
markCallHistoryDeleted: (callId: string) => Promise<void>;
clearCallHistory: (beforeTimestamp: number) => Promise<Array<string>>;
cleanupCallHistoryMessages: () => Promise<void>;
getCallHistoryUnreadCount(): Promise<number>;
markCallHistoryRead(callId: string): Promise<void>;
markAllCallHistoryRead(
beforeTimestamp: number
): Promise<ReadonlyArray<string>>;
markAllCallHistoryRead(target: CallLogEventTarget): Promise<void>;
markAllCallHistoryReadInConversation(
target: CallLogEventTarget
): Promise<void>;
getCallHistoryMessageByCallId(options: {
conversationId: string;
callId: string;

View file

@ -157,6 +157,7 @@ import type {
CallHistoryFilter,
CallHistoryGroup,
CallHistoryPagination,
CallLogEventTarget,
} from '../types/CallDisposition';
import {
DirectCallStatus,
@ -166,6 +167,7 @@ import {
CallDirection,
GroupCallStatus,
CallType,
CallStatusValue,
} from '../types/CallDisposition';
import {
callLinkExists,
@ -352,6 +354,7 @@ const dataInterface: ServerInterface = {
getCallHistoryUnreadCount,
markCallHistoryRead,
markAllCallHistoryRead,
markAllCallHistoryReadInConversation,
getCallHistoryMessageByCallId,
getCallHistory,
getCallHistoryGroupsCount,
@ -3631,34 +3634,56 @@ async function getAllCallHistory(): Promise<ReadonlyArray<CallHistoryDetails>> {
}
async function clearCallHistory(
beforeTimestamp: number
): Promise<Array<string>> {
target: CallLogEventTarget
): Promise<ReadonlyArray<string>> {
const db = await getWritableInstance();
return db.transaction(() => {
const whereMessages = sqlFragment`
WHERE messages.type IS 'call-history'
AND messages.sent_at <= ${beforeTimestamp};
const timestamp = getTimestampForCallLogEventTarget(db, target);
const [selectCallIdsQuery, selectCallIdsParams] = sql`
SELECT callsHistory.callId
FROM callsHistory
WHERE
-- Prior calls
(callsHistory.timestamp <= ${timestamp})
-- Unused call links
OR (
callsHistory.mode IS ${CALL_MODE_ADHOC} AND
callsHistory.status IS ${CALL_STATUS_PENDING}
);
`;
const [selectMessagesQuery, selectMessagesParams] = sql`
SELECT id FROM messages ${whereMessages}
`;
const [clearMessagesQuery, clearMessagesParams] = sql`
DELETE FROM messages ${whereMessages}
`;
const callIds = db
.prepare(selectCallIdsQuery)
.pluck()
.all(selectCallIdsParams);
let deletedMessageIds: ReadonlyArray<string> = [];
batchMultiVarQuery(db, callIds, (ids: ReadonlyArray<string>): void => {
const [deleteMessagesQuery, deleteMessagesParams] = sql`
DELETE FROM messages
WHERE messages.type IS 'call-history'
AND messages.callId IN (${sqlJoin(ids)})
RETURNING id;
`;
const batchDeletedMessageIds = db
.prepare(deleteMessagesQuery)
.pluck()
.all(deleteMessagesParams);
deletedMessageIds = deletedMessageIds.concat(batchDeletedMessageIds);
});
const [clearCallsHistoryQuery, clearCallsHistoryParams] = sql`
UPDATE callsHistory
SET
status = ${DirectCallStatus.Deleted},
timestamp = ${Date.now()}
WHERE callsHistory.timestamp <= ${beforeTimestamp};
WHERE callsHistory.timestamp <= ${timestamp};
`;
const messageIds = db
.prepare(selectMessagesQuery)
.pluck()
.all(selectMessagesParams);
db.prepare(clearMessagesQuery).run(clearMessagesParams);
try {
db.prepare(clearCallsHistoryQuery).run(clearCallsHistoryParams);
} catch (error) {
@ -3666,7 +3691,7 @@ async function clearCallHistory(
throw error;
}
return messageIds;
return deletedMessageIds;
})();
}
@ -3743,8 +3768,9 @@ async function getCallHistory(
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
const SEEN_STATUS_SEEN = sqlConstant(SeenStatus.Seen);
const CALL_STATUS_MISSED = sqlConstant(DirectCallStatus.Missed);
const CALL_STATUS_DELETED = sqlConstant(DirectCallStatus.Deleted);
const CALL_STATUS_MISSED = sqlConstant(CallStatusValue.Missed);
const CALL_STATUS_DELETED = sqlConstant(CallStatusValue.Deleted);
const CALL_STATUS_PENDING = sqlConstant(CallStatusValue.Pending);
const CALL_STATUS_INCOMING = sqlConstant(CallDirection.Incoming);
const CALL_MODE_ADHOC = sqlConstant(CallMode.Adhoc);
const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000);
@ -3781,44 +3807,88 @@ async function markCallHistoryRead(callId: string): Promise<void> {
db.prepare(query).run(params);
}
async function markAllCallHistoryRead(
beforeTimestamp: number
): Promise<ReadonlyArray<string>> {
const db = await getWritableInstance();
return db.transaction(() => {
const where = sqlFragment`
WHERE messages.type IS 'call-history'
AND messages.seenStatus IS ${SEEN_STATUS_UNSEEN}
AND messages.sent_at <= ${beforeTimestamp};
`;
function getTimestampForCallLogEventTarget(
db: Database,
target: CallLogEventTarget
): number {
let { timestamp } = target;
if (target.peerId != null && target.callId != null) {
const [selectQuery, selectParams] = sql`
SELECT DISTINCT conversationId
FROM messages
${where};
SELECT callsHistory.timestamp
FROM callsHistory
WHERE callsHistory.callId IS ${target.callId}
AND callsHistory.peerId IS ${target.peerId}
`;
const value = db.prepare(selectQuery).pluck().get(selectParams);
const conversationIds = db.prepare(selectQuery).pluck().all(selectParams);
if (value != null) {
timestamp = value;
} else {
log.warn(
'getTimestampForCallLogEventTarget: Target call not found',
target.callId
);
}
}
return timestamp;
}
async function markAllCallHistoryReadWithPredicate(
target: CallLogEventTarget,
inConversation: boolean
) {
const db = await getWritableInstance();
db.transaction(() => {
const jsonPatch = JSON.stringify({
seenStatus: SeenStatus.Seen,
});
const [updateQuery, updateParams] = sql`
UPDATE messages
SET
seenStatus = ${SEEN_STATUS_SEEN},
json = json_patch(json, ${jsonPatch})
${where};
const timestamp = getTimestampForCallLogEventTarget(db, target);
const predicate = inConversation
? sqlFragment`callsHistory.peerId IS ${target.peerId}`
: sqlFragment`TRUE`;
const [selectQuery, selectParams] = sql`
SELECT callsHistory.callId
FROM callsHistory
WHERE ${predicate}
AND callsHistory.timestamp <= ${timestamp}
`;
db.prepare(updateQuery).run(updateParams);
const callIds = db.prepare(selectQuery).pluck().all(selectParams);
return conversationIds;
batchMultiVarQuery(db, callIds, ids => {
const idList = sqlJoin(ids.map(id => sqlFragment`${id}`));
const [updateQuery, updateParams] = sql`
UPDATE messages
SET
seenStatus = ${SEEN_STATUS_SEEN},
json = json_patch(json, ${jsonPatch})
WHERE callId IN (${idList});
`;
db.prepare(updateQuery).run(updateParams);
});
})();
}
async function markAllCallHistoryRead(
target: CallLogEventTarget
): Promise<void> {
await markAllCallHistoryReadWithPredicate(target, false);
}
async function markAllCallHistoryReadInConversation(
target: CallLogEventTarget
): Promise<void> {
strictAssert(target.peerId, 'peerId is required');
await markAllCallHistoryReadWithPredicate(target, true);
}
function getCallHistoryGroupDataSync(
db: Database,
isCount: boolean,
@ -4932,7 +5002,7 @@ async function saveAttachmentBackupJob(
attempts,
data,
lastAttemptTimestamp,
mediaName,
mediaName,
receivedAt,
retryAfter,
type
@ -5003,7 +5073,7 @@ function removeAttachmentBackupJobSync(
): void {
const [query, params] = sql`
DELETE FROM attachment_backup_jobs
WHERE
WHERE
mediaName = ${job.mediaName};
`;

View file

@ -18,6 +18,10 @@ import type { CallHistoryDetails } from '../../types/CallDisposition';
import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
import { drop } from '../../util/drop';
import {
getCallHistoryLatestCall,
getCallHistorySelector,
} from '../selectors/callHistory';
export type CallHistoryState = ReadonlyDeep<{
// This informs the app that underlying call history data has changed.
@ -103,15 +107,35 @@ function markCallHistoryRead(
};
}
export function markCallHistoryReadInConversation(
callId: string
): ThunkAction<void, RootStateType, unknown, CallHistoryUpdateUnread> {
return async (dispatch, getState) => {
const callHistorySelector = getCallHistorySelector(getState());
const callHistory = callHistorySelector(callId);
if (callHistory == null) {
return;
}
try {
await markAllCallHistoryReadAndSync(callHistory, true);
} finally {
dispatch(updateCallHistoryUnreadCount());
}
};
}
function markCallsTabViewed(): ThunkAction<
void,
RootStateType,
unknown,
CallHistoryUpdateUnread
> {
return async dispatch => {
await markAllCallHistoryReadAndSync();
dispatch(updateCallHistoryUnreadCount());
return async (dispatch, getState) => {
const latestCall = getCallHistoryLatestCall(getState());
if (latestCall != null) {
await markAllCallHistoryReadAndSync(latestCall, false);
dispatch(updateCallHistoryUnreadCount());
}
};
}
@ -143,10 +167,13 @@ function clearAllCallHistory(): ThunkAction<
unknown,
CallHistoryReset | ToastActionType
> {
return async dispatch => {
return async (dispatch, getState) => {
try {
await clearCallHistoryDataAndSync();
dispatch(showToast({ toastType: ToastType.CallHistoryCleared }));
const latestCall = getCallHistoryLatestCall(getState());
if (latestCall != null) {
await clearCallHistoryDataAndSync(latestCall);
dispatch(showToast({ toastType: ToastType.CallHistoryCleared }));
}
} catch (error) {
log.error('Error clearing call history', Errors.toLogFormat(error));
} finally {

View file

@ -195,6 +195,7 @@ import {
} from '../../util/deleteForMe';
import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types';
import { isEnabled } from '../../RemoteConfig';
import { markCallHistoryReadInConversation } from './callHistory';
import type { CapabilitiesType } from '../../textsecure/WebAPI';
// State
@ -1358,7 +1359,7 @@ function markMessageRead(
conversationId: string,
messageId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async (_dispatch, getState) => {
return async (dispatch, getState) => {
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('markMessageRead: Conversation not found!');
@ -1382,6 +1383,12 @@ function markMessageRead(
newestSentAt: message.get('sent_at'),
sendReadReceipts: true,
});
if (message.get('type') === 'call-history') {
const callId = message.get('callId');
strictAssert(callId, 'callId not found');
dispatch(markCallHistoryReadInConversation(callId));
}
};
}

View file

@ -4,7 +4,11 @@
import { createSelector } from 'reselect';
import type { CallHistoryState } from '../ducks/callHistory';
import type { StateType } from '../reducer';
import type { CallHistoryDetails } from '../../types/CallDisposition';
import {
AdhocCallStatus,
CallType,
type CallHistoryDetails,
} from '../../types/CallDisposition';
import { getOwn } from '../../util/getOwn';
const getCallHistory = (state: StateType): CallHistoryState =>
@ -36,3 +40,28 @@ export const getCallHistoryUnreadCount = createSelector(
return callHistory.unreadCount;
}
);
export const getCallHistoryLatestCall = createSelector(
getCallHistory,
callHistory => {
let latestCall = null;
for (const callId of Object.keys(callHistory.callHistoryByCallId)) {
const call = callHistory.callHistoryByCallId[callId];
// Skip unused call links
if (
call.type === CallType.Adhoc &&
call.status === AdhocCallStatus.Pending
) {
continue;
}
if (latestCall == null || call.timestamp > latestCall.timestamp) {
latestCall = call;
}
}
return latestCall;
}
);

View file

@ -152,9 +152,11 @@ import { chunk } from '../util/iterables';
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
import { incrementMessageCounter } from '../util/incrementMessageCounter';
import { filterAndClean } from '../types/BodyRange';
import { getCallEventForProto } from '../util/callDisposition';
import {
getCallEventForProto,
getCallLogEventForProto,
} from '../util/callDisposition';
import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey';
import { CallLogEvent } from '../types/CallDisposition';
import { CallLinkUpdateSyncType } from '../types/CallLink';
import { bytesToUuid } from '../util/uuidToBytes';
@ -3614,32 +3616,10 @@ export default class MessageReceiver
const { receivedAtCounter } = envelope;
let event: CallLogEvent;
if (callLogEvent.type == null) {
throw new Error('MessageReceiver.handleCallLogEvent: type was null');
} else if (
callLogEvent.type === Proto.SyncMessage.CallLogEvent.Type.CLEAR
) {
event = CallLogEvent.Clear;
} else if (
callLogEvent.type === Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ
) {
event = CallLogEvent.MarkedAsRead;
} else {
throw new Error(
`MessageReceiver.handleCallLogEvent: unknown type ${callLogEvent.type}`
);
}
if (callLogEvent.timestamp == null) {
throw new Error('MessageReceiver.handleCallLogEvent: timestamp was null');
}
const timestamp = callLogEvent.timestamp.toNumber();
const callLogEventDetails = getCallLogEventForProto(callLogEvent);
const callLogEventSync = new CallLogEventSyncEvent(
{
event,
timestamp,
callLogEventDetails,
receivedAtCounter,
},
this.removeFromCache.bind(this, envelope)

View file

@ -89,13 +89,16 @@ import type {
MessageToDelete,
} from './messageReceiverEvents';
import { getConversationFromTarget } from '../util/deleteForMe';
import type { CallDetails } from '../types/CallDisposition';
import type { CallDetails, CallHistoryDetails } from '../types/CallDisposition';
import {
AdhocCallStatus,
DirectCallStatus,
GroupCallStatus,
} from '../types/CallDisposition';
import { getProtoForCallHistory } from '../util/callDisposition';
import {
getBytesForPeerId,
getProtoForCallHistory,
} from '../util/callDisposition';
import { CallMode } from '../types/Calling';
import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types';
@ -1589,11 +1592,15 @@ export default class MessageSender {
};
}
static getClearCallHistoryMessage(timestamp: number): SingleProtoJobData {
static getClearCallHistoryMessage(
latestCall: CallHistoryDetails
): SingleProtoJobData {
const ourAci = window.textsecure.storage.user.getCheckedAci();
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
type: Proto.SyncMessage.CallLogEvent.Type.CLEAR,
timestamp: Long.fromNumber(timestamp),
timestamp: Long.fromNumber(latestCall.timestamp),
peerId: getBytesForPeerId(latestCall),
callId: Long.fromString(latestCall.callId),
});
const syncMessage = MessageSender.createSyncMessage();

View file

@ -18,7 +18,10 @@ import type {
ProcessedSent,
} from './Types.d';
import type { ContactDetailsWithAvatar } from './ContactsParser';
import type { CallEventDetails, CallLogEvent } from '../types/CallDisposition';
import type {
CallEventDetails,
CallLogEventDetails,
} from '../types/CallDisposition';
import type { CallLinkUpdateSyncType } from '../types/CallLink';
import { isAciString } from '../util/isAciString';
@ -559,14 +562,13 @@ export class DeleteForMeSyncEvent extends ConfirmableEvent {
}
export type CallLogEventSyncEventData = Readonly<{
event: CallLogEvent;
timestamp: number;
callLogEventDetails: CallLogEventDetails;
receivedAtCounter: number;
}>;
export class CallLogEventSyncEvent extends ConfirmableEvent {
constructor(
public readonly callLogEvent: CallLogEventSyncEventData,
public readonly data: CallLogEventSyncEventData,
confirm: ConfirmCallback
) {
super('callLogEventSync', confirm);

View file

@ -26,6 +26,7 @@ export enum CallDirection {
export enum CallLogEvent {
Clear = 'Clear',
MarkedAsRead = 'MarkedAsRead',
MarkedAsReadInConversation = 'MarkedAsReadInConversation',
}
export enum LocalCallEvent {
@ -97,6 +98,19 @@ export type CallDetails = Readonly<{
timestamp: number;
}>;
export type CallLogEventTarget = Readonly<{
timestamp: number;
callId: string | null;
peerId: AciString | string | null;
}>;
export type CallLogEventDetails = Readonly<{
type: CallLogEvent;
timestamp: number;
peerId: AciString | string | null;
callId: string | null;
}>;
export type CallEventDetails = CallDetails &
Readonly<{
event: CallEvent;
@ -221,6 +235,13 @@ export const callEventNormalizeSchema = z.object({
event: z.nativeEnum(Proto.SyncMessage.CallEvent.Event),
});
export const callLogEventNormalizeSchema = z.object({
type: z.nativeEnum(Proto.SyncMessage.CallLogEvent.Type),
timestamp: longToNumberSchema,
peerId: peerIdInBytesSchema.optional(),
callId: longToStringSchema.optional(),
});
export function isSameCallHistoryGroup(
a: CallHistoryGroup,
b: CallHistoryGroup

View file

@ -44,6 +44,7 @@ import type {
CallEventDetails,
CallHistoryDetails,
CallHistoryGroup,
CallLogEventDetails,
CallStatus,
GroupCallMeta,
} from '../types/CallDisposition';
@ -60,6 +61,8 @@ import {
callDetailsSchema,
AdhocCallStatus,
CallStatusValue,
callLogEventNormalizeSchema,
CallLogEvent,
} from '../types/CallDisposition';
import type { ConversationType } from '../state/ducks/conversations';
import type { ConversationModel } from '../models/conversations';
@ -248,6 +251,34 @@ export function getCallEventForProto(
});
}
const callLogEventFromProto: Partial<
Record<Proto.SyncMessage.CallLogEvent.Type, CallLogEvent>
> = {
[Proto.SyncMessage.CallLogEvent.Type.CLEAR]: CallLogEvent.Clear,
[Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ]:
CallLogEvent.MarkedAsRead,
[Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ_IN_CONVERSATION]:
CallLogEvent.MarkedAsReadInConversation,
};
export function getCallLogEventForProto(
callLogEventProto: Proto.SyncMessage.ICallLogEvent
): CallLogEventDetails {
const callLogEvent = callLogEventNormalizeSchema.parse(callLogEventProto);
const type = callLogEventFromProto[callLogEvent.type];
if (type == null) {
throw new TypeError(`Unknown call log event ${callLogEvent.type}`);
}
return {
type,
timestamp: callLogEvent.timestamp,
peerId: callLogEvent.peerId ?? null,
callId: callLogEvent.callId ?? null,
};
}
const directionToProto = {
[CallDirection.Incoming]: Proto.SyncMessage.CallEvent.Direction.INCOMING,
[CallDirection.Outgoing]: Proto.SyncMessage.CallEvent.Direction.OUTGOING,
@ -280,6 +311,17 @@ function shouldSyncStatus(callStatus: CallStatus) {
return statusToProto[callStatus] != null;
}
export function getBytesForPeerId(callHistory: CallHistoryDetails): Uint8Array {
let peerId =
callHistory.mode === CallMode.Adhoc
? Bytes.fromBase64(callHistory.peerId)
: uuidToBytes(callHistory.peerId);
if (peerId.length === 0) {
peerId = Bytes.fromBase64(callHistory.peerId);
}
return peerId;
}
export function getProtoForCallHistory(
callHistory: CallHistoryDetails
): Proto.SyncMessage.ICallEvent | null {
@ -292,16 +334,8 @@ export function getProtoForCallHistory(
)}`
);
let peerId =
callHistory.mode === CallMode.Adhoc
? Bytes.fromBase64(callHistory.peerId)
: uuidToBytes(callHistory.peerId);
if (peerId.length === 0) {
peerId = Bytes.fromBase64(callHistory.peerId);
}
return new Proto.SyncMessage.CallEvent({
peerId,
peerId: getBytesForPeerId(callHistory),
callId: Long.fromString(callHistory.callId),
type: typeToProto[callHistory.type],
direction: directionToProto[callHistory.direction],
@ -1191,50 +1225,63 @@ export async function updateCallHistoryFromLocalEvent(
await updateRemoteCallHistory(updatedCallHistory);
}
export async function clearCallHistoryDataAndSync(): Promise<void> {
export function updateDeletedMessages(messageIds: ReadonlyArray<string>): void {
messageIds.forEach(messageId => {
const message = window.MessageCache.__DEPRECATED$getById(messageId);
const conversation = message?.getConversation();
if (message == null || conversation == null) {
return;
}
window.reduxActions.conversations.messageDeleted(
messageId,
message.get('conversationId')
);
conversation.debouncedUpdateLastMessage();
window.MessageCache.__DEPRECATED$unregister(messageId);
});
}
export async function clearCallHistoryDataAndSync(
latestCall: CallHistoryDetails
): Promise<void> {
try {
const timestamp = Date.now();
log.info(`clearCallHistory: Clearing call history before ${timestamp}`);
const messageIds = await window.Signal.Data.clearCallHistory(timestamp);
messageIds.forEach(messageId => {
const message = window.MessageCache.__DEPRECATED$getById(messageId);
const conversation = message?.getConversation();
if (message == null || conversation == null) {
return;
}
window.reduxActions.conversations.messageDeleted(
messageId,
message.get('conversationId')
);
conversation.debouncedUpdateLastMessage();
window.MessageCache.__DEPRECATED$unregister(messageId);
});
log.info(
`clearCallHistory: Clearing call history before (${latestCall.callId}, ${latestCall.timestamp})`
);
const messageIds = await window.Signal.Data.clearCallHistory(latestCall);
updateDeletedMessages(messageIds);
log.info('clearCallHistory: Queueing sync message');
await singleProtoJobQueue.add(
MessageSender.getClearCallHistoryMessage(timestamp)
MessageSender.getClearCallHistoryMessage(latestCall)
);
} catch (error) {
log.error('clearCallHistory: Failed to clear call history', error);
}
}
export async function markAllCallHistoryReadAndSync(): Promise<void> {
export async function markAllCallHistoryReadAndSync(
latestCall: CallHistoryDetails,
inConversation: boolean
): Promise<void> {
try {
const timestamp = Date.now();
log.info(
`markAllCallHistoryReadAndSync: Marking call history read before ${timestamp}`
`markAllCallHistoryReadAndSync: Marking call history read before (${latestCall.callId}, ${latestCall.timestamp})`
);
await window.Signal.Data.markAllCallHistoryRead(timestamp);
if (inConversation) {
await window.Signal.Data.markAllCallHistoryReadInConversation(latestCall);
} else {
await window.Signal.Data.markAllCallHistoryRead(latestCall);
}
const ourAci = window.textsecure.storage.user.getCheckedAci();
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
type: Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ,
timestamp: Long.fromNumber(timestamp),
type: inConversation
? Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ_IN_CONVERSATION
: Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ,
timestamp: Long.fromNumber(latestCall.timestamp),
peerId: getBytesForPeerId(latestCall),
callId: Long.fromString(latestCall.callId),
});
const syncMessage = MessageSender.createSyncMessage();

View file

@ -3,39 +3,56 @@
import type { CallLogEventSyncEvent } from '../textsecure/messageReceiverEvents';
import * as log from '../logging/log';
import type { CallLogEventTarget } from '../types/CallDisposition';
import { CallLogEvent } from '../types/CallDisposition';
import { missingCaseError } from './missingCaseError';
import { strictAssert } from './assert';
import { updateDeletedMessages } from './callDisposition';
export async function onCallLogEventSync(
syncEvent: CallLogEventSyncEvent
): Promise<void> {
const { callLogEvent, confirm } = syncEvent;
const { event, timestamp } = callLogEvent;
const { data, confirm } = syncEvent;
const { type, peerId, callId, timestamp } = data.callLogEventDetails;
const target: CallLogEventTarget = {
peerId,
callId,
timestamp,
};
log.info(
`onCallLogEventSync: Processing event (Event: ${event}, Timestamp: ${timestamp})`
`onCallLogEventSync: Processing event (Event: ${type}, CallId: ${callId}, Timestamp: ${timestamp})`
);
if (event === CallLogEvent.Clear) {
log.info(`onCallLogEventSync: Clearing call history before ${timestamp}`);
if (type === CallLogEvent.Clear) {
log.info('onCallLogEventSync: Clearing call history');
try {
await window.Signal.Data.clearCallHistory(timestamp);
const messageIds = await window.Signal.Data.clearCallHistory(target);
updateDeletedMessages(messageIds);
} finally {
// We want to reset the call history even if the clear fails.
window.reduxActions.callHistory.resetCallHistory();
}
confirm();
} else if (event === CallLogEvent.MarkedAsRead) {
log.info(
`onCallLogEventSync: Marking call history read before ${timestamp}`
);
} else if (type === CallLogEvent.MarkedAsRead) {
log.info('onCallLogEventSync: Marking call history read');
try {
await window.Signal.Data.markAllCallHistoryRead(timestamp);
await window.Signal.Data.markAllCallHistoryRead(target);
} finally {
window.reduxActions.callHistory.updateCallHistoryUnreadCount();
}
confirm();
} else if (type === CallLogEvent.MarkedAsReadInConversation) {
log.info('onCallLogEventSync: Marking call history read in conversation');
try {
strictAssert(peerId, 'Missing peerId');
await window.Signal.Data.markAllCallHistoryReadInConversation(target);
} finally {
window.reduxActions.callHistory.updateCallHistoryUnreadCount();
}
confirm();
} else {
throw missingCaseError(event);
throw missingCaseError(type);
}
}