Update CallLogEvent to latest spec
This commit is contained in:
parent
815fd77849
commit
fc08e70c0f
12 changed files with 366 additions and 142 deletions
|
@ -604,7 +604,12 @@ message SyncMessage {
|
||||||
DELETE = 3;
|
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;
|
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 callId = 2;
|
||||||
optional uint64 timestamp = 3;
|
optional uint64 timestamp = 3;
|
||||||
optional Type type = 4;
|
optional Type type = 4;
|
||||||
|
@ -627,10 +632,18 @@ message SyncMessage {
|
||||||
enum Type {
|
enum Type {
|
||||||
CLEAR = 0;
|
CLEAR = 0;
|
||||||
MARKED_AS_READ = 1;
|
MARKED_AS_READ = 1;
|
||||||
|
MARKED_AS_READ_IN_CONVERSATION = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
optional uint64 timestamp = 2;
|
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 {
|
message DeleteForMe {
|
||||||
|
@ -641,7 +654,7 @@ message SyncMessage {
|
||||||
string threadE164 = 3;
|
string threadE164 = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddressableMessage {
|
message AddressableMessage {
|
||||||
oneof author {
|
oneof author {
|
||||||
string authorServiceId = 1;
|
string authorServiceId = 1;
|
||||||
|
@ -682,7 +695,7 @@ message SyncMessage {
|
||||||
repeated LocalOnlyConversationDelete localOnlyConversationDeletes = 3;
|
repeated LocalOnlyConversationDelete localOnlyConversationDeletes = 3;
|
||||||
repeated AttachmentDelete attachmentDeletes = 4;
|
repeated AttachmentDelete attachmentDeletes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Sent sent = 1;
|
optional Sent sent = 1;
|
||||||
optional Contacts contacts = 2;
|
optional Contacts contacts = 2;
|
||||||
reserved /* groups */ 3;
|
reserved /* groups */ 3;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import type {
|
||||||
CallHistoryFilter,
|
CallHistoryFilter,
|
||||||
CallHistoryGroup,
|
CallHistoryGroup,
|
||||||
CallHistoryPagination,
|
CallHistoryPagination,
|
||||||
|
CallLogEventTarget,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
|
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
|
||||||
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
|
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
|
||||||
|
@ -666,14 +667,17 @@ export type DataInterface = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
}): Promise<MessageType | undefined>;
|
}): Promise<MessageType | undefined>;
|
||||||
getAllCallHistory: () => Promise<ReadonlyArray<CallHistoryDetails>>;
|
getAllCallHistory: () => Promise<ReadonlyArray<CallHistoryDetails>>;
|
||||||
|
clearCallHistory: (
|
||||||
|
target: CallLogEventTarget
|
||||||
|
) => Promise<ReadonlyArray<string>>;
|
||||||
markCallHistoryDeleted: (callId: string) => Promise<void>;
|
markCallHistoryDeleted: (callId: string) => Promise<void>;
|
||||||
clearCallHistory: (beforeTimestamp: number) => Promise<Array<string>>;
|
|
||||||
cleanupCallHistoryMessages: () => Promise<void>;
|
cleanupCallHistoryMessages: () => Promise<void>;
|
||||||
getCallHistoryUnreadCount(): Promise<number>;
|
getCallHistoryUnreadCount(): Promise<number>;
|
||||||
markCallHistoryRead(callId: string): Promise<void>;
|
markCallHistoryRead(callId: string): Promise<void>;
|
||||||
markAllCallHistoryRead(
|
markAllCallHistoryRead(target: CallLogEventTarget): Promise<void>;
|
||||||
beforeTimestamp: number
|
markAllCallHistoryReadInConversation(
|
||||||
): Promise<ReadonlyArray<string>>;
|
target: CallLogEventTarget
|
||||||
|
): Promise<void>;
|
||||||
getCallHistoryMessageByCallId(options: {
|
getCallHistoryMessageByCallId(options: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
callId: string;
|
callId: string;
|
||||||
|
|
160
ts/sql/Server.ts
160
ts/sql/Server.ts
|
@ -157,6 +157,7 @@ import type {
|
||||||
CallHistoryFilter,
|
CallHistoryFilter,
|
||||||
CallHistoryGroup,
|
CallHistoryGroup,
|
||||||
CallHistoryPagination,
|
CallHistoryPagination,
|
||||||
|
CallLogEventTarget,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
import {
|
import {
|
||||||
DirectCallStatus,
|
DirectCallStatus,
|
||||||
|
@ -166,6 +167,7 @@ import {
|
||||||
CallDirection,
|
CallDirection,
|
||||||
GroupCallStatus,
|
GroupCallStatus,
|
||||||
CallType,
|
CallType,
|
||||||
|
CallStatusValue,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
import {
|
import {
|
||||||
callLinkExists,
|
callLinkExists,
|
||||||
|
@ -352,6 +354,7 @@ const dataInterface: ServerInterface = {
|
||||||
getCallHistoryUnreadCount,
|
getCallHistoryUnreadCount,
|
||||||
markCallHistoryRead,
|
markCallHistoryRead,
|
||||||
markAllCallHistoryRead,
|
markAllCallHistoryRead,
|
||||||
|
markAllCallHistoryReadInConversation,
|
||||||
getCallHistoryMessageByCallId,
|
getCallHistoryMessageByCallId,
|
||||||
getCallHistory,
|
getCallHistory,
|
||||||
getCallHistoryGroupsCount,
|
getCallHistoryGroupsCount,
|
||||||
|
@ -3631,34 +3634,56 @@ async function getAllCallHistory(): Promise<ReadonlyArray<CallHistoryDetails>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearCallHistory(
|
async function clearCallHistory(
|
||||||
beforeTimestamp: number
|
target: CallLogEventTarget
|
||||||
): Promise<Array<string>> {
|
): Promise<ReadonlyArray<string>> {
|
||||||
const db = await getWritableInstance();
|
const db = await getWritableInstance();
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const whereMessages = sqlFragment`
|
const timestamp = getTimestampForCallLogEventTarget(db, target);
|
||||||
WHERE messages.type IS 'call-history'
|
|
||||||
AND messages.sent_at <= ${beforeTimestamp};
|
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`
|
const callIds = db
|
||||||
SELECT id FROM messages ${whereMessages}
|
.prepare(selectCallIdsQuery)
|
||||||
`;
|
.pluck()
|
||||||
const [clearMessagesQuery, clearMessagesParams] = sql`
|
.all(selectCallIdsParams);
|
||||||
DELETE FROM messages ${whereMessages}
|
|
||||||
`;
|
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`
|
const [clearCallsHistoryQuery, clearCallsHistoryParams] = sql`
|
||||||
UPDATE callsHistory
|
UPDATE callsHistory
|
||||||
SET
|
SET
|
||||||
status = ${DirectCallStatus.Deleted},
|
status = ${DirectCallStatus.Deleted},
|
||||||
timestamp = ${Date.now()}
|
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 {
|
try {
|
||||||
db.prepare(clearCallsHistoryQuery).run(clearCallsHistoryParams);
|
db.prepare(clearCallsHistoryQuery).run(clearCallsHistoryParams);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -3666,7 +3691,7 @@ async function clearCallHistory(
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageIds;
|
return deletedMessageIds;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3743,8 +3768,9 @@ async function getCallHistory(
|
||||||
|
|
||||||
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
|
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
|
||||||
const SEEN_STATUS_SEEN = sqlConstant(SeenStatus.Seen);
|
const SEEN_STATUS_SEEN = sqlConstant(SeenStatus.Seen);
|
||||||
const CALL_STATUS_MISSED = sqlConstant(DirectCallStatus.Missed);
|
const CALL_STATUS_MISSED = sqlConstant(CallStatusValue.Missed);
|
||||||
const CALL_STATUS_DELETED = sqlConstant(DirectCallStatus.Deleted);
|
const CALL_STATUS_DELETED = sqlConstant(CallStatusValue.Deleted);
|
||||||
|
const CALL_STATUS_PENDING = sqlConstant(CallStatusValue.Pending);
|
||||||
const CALL_STATUS_INCOMING = sqlConstant(CallDirection.Incoming);
|
const CALL_STATUS_INCOMING = sqlConstant(CallDirection.Incoming);
|
||||||
const CALL_MODE_ADHOC = sqlConstant(CallMode.Adhoc);
|
const CALL_MODE_ADHOC = sqlConstant(CallMode.Adhoc);
|
||||||
const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000);
|
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);
|
db.prepare(query).run(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markAllCallHistoryRead(
|
function getTimestampForCallLogEventTarget(
|
||||||
beforeTimestamp: number
|
db: Database,
|
||||||
): Promise<ReadonlyArray<string>> {
|
target: CallLogEventTarget
|
||||||
const db = await getWritableInstance();
|
): number {
|
||||||
|
let { timestamp } = target;
|
||||||
return db.transaction(() => {
|
|
||||||
const where = sqlFragment`
|
|
||||||
WHERE messages.type IS 'call-history'
|
|
||||||
AND messages.seenStatus IS ${SEEN_STATUS_UNSEEN}
|
|
||||||
AND messages.sent_at <= ${beforeTimestamp};
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
if (target.peerId != null && target.callId != null) {
|
||||||
const [selectQuery, selectParams] = sql`
|
const [selectQuery, selectParams] = sql`
|
||||||
SELECT DISTINCT conversationId
|
SELECT callsHistory.timestamp
|
||||||
FROM messages
|
FROM callsHistory
|
||||||
${where};
|
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({
|
const jsonPatch = JSON.stringify({
|
||||||
seenStatus: SeenStatus.Seen,
|
seenStatus: SeenStatus.Seen,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [updateQuery, updateParams] = sql`
|
const timestamp = getTimestampForCallLogEventTarget(db, target);
|
||||||
UPDATE messages
|
|
||||||
SET
|
const predicate = inConversation
|
||||||
seenStatus = ${SEEN_STATUS_SEEN},
|
? sqlFragment`callsHistory.peerId IS ${target.peerId}`
|
||||||
json = json_patch(json, ${jsonPatch})
|
: sqlFragment`TRUE`;
|
||||||
${where};
|
|
||||||
|
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(
|
function getCallHistoryGroupDataSync(
|
||||||
db: Database,
|
db: Database,
|
||||||
isCount: boolean,
|
isCount: boolean,
|
||||||
|
@ -4932,7 +5002,7 @@ async function saveAttachmentBackupJob(
|
||||||
attempts,
|
attempts,
|
||||||
data,
|
data,
|
||||||
lastAttemptTimestamp,
|
lastAttemptTimestamp,
|
||||||
mediaName,
|
mediaName,
|
||||||
receivedAt,
|
receivedAt,
|
||||||
retryAfter,
|
retryAfter,
|
||||||
type
|
type
|
||||||
|
@ -5003,7 +5073,7 @@ function removeAttachmentBackupJobSync(
|
||||||
): void {
|
): void {
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
DELETE FROM attachment_backup_jobs
|
DELETE FROM attachment_backup_jobs
|
||||||
WHERE
|
WHERE
|
||||||
mediaName = ${job.mediaName};
|
mediaName = ${job.mediaName};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ import type { CallHistoryDetails } from '../../types/CallDisposition';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
import { drop } from '../../util/drop';
|
import { drop } from '../../util/drop';
|
||||||
|
import {
|
||||||
|
getCallHistoryLatestCall,
|
||||||
|
getCallHistorySelector,
|
||||||
|
} from '../selectors/callHistory';
|
||||||
|
|
||||||
export type CallHistoryState = ReadonlyDeep<{
|
export type CallHistoryState = ReadonlyDeep<{
|
||||||
// This informs the app that underlying call history data has changed.
|
// 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<
|
function markCallsTabViewed(): ThunkAction<
|
||||||
void,
|
void,
|
||||||
RootStateType,
|
RootStateType,
|
||||||
unknown,
|
unknown,
|
||||||
CallHistoryUpdateUnread
|
CallHistoryUpdateUnread
|
||||||
> {
|
> {
|
||||||
return async dispatch => {
|
return async (dispatch, getState) => {
|
||||||
await markAllCallHistoryReadAndSync();
|
const latestCall = getCallHistoryLatestCall(getState());
|
||||||
dispatch(updateCallHistoryUnreadCount());
|
if (latestCall != null) {
|
||||||
|
await markAllCallHistoryReadAndSync(latestCall, false);
|
||||||
|
dispatch(updateCallHistoryUnreadCount());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,10 +167,13 @@ function clearAllCallHistory(): ThunkAction<
|
||||||
unknown,
|
unknown,
|
||||||
CallHistoryReset | ToastActionType
|
CallHistoryReset | ToastActionType
|
||||||
> {
|
> {
|
||||||
return async dispatch => {
|
return async (dispatch, getState) => {
|
||||||
try {
|
try {
|
||||||
await clearCallHistoryDataAndSync();
|
const latestCall = getCallHistoryLatestCall(getState());
|
||||||
dispatch(showToast({ toastType: ToastType.CallHistoryCleared }));
|
if (latestCall != null) {
|
||||||
|
await clearCallHistoryDataAndSync(latestCall);
|
||||||
|
dispatch(showToast({ toastType: ToastType.CallHistoryCleared }));
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Error clearing call history', Errors.toLogFormat(error));
|
log.error('Error clearing call history', Errors.toLogFormat(error));
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -195,6 +195,7 @@ import {
|
||||||
} from '../../util/deleteForMe';
|
} from '../../util/deleteForMe';
|
||||||
import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types';
|
import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types';
|
||||||
import { isEnabled } from '../../RemoteConfig';
|
import { isEnabled } from '../../RemoteConfig';
|
||||||
|
import { markCallHistoryReadInConversation } from './callHistory';
|
||||||
import type { CapabilitiesType } from '../../textsecure/WebAPI';
|
import type { CapabilitiesType } from '../../textsecure/WebAPI';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
@ -1358,7 +1359,7 @@ function markMessageRead(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async (_dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('markMessageRead: Conversation not found!');
|
throw new Error('markMessageRead: Conversation not found!');
|
||||||
|
@ -1382,6 +1383,12 @@ function markMessageRead(
|
||||||
newestSentAt: message.get('sent_at'),
|
newestSentAt: message.get('sent_at'),
|
||||||
sendReadReceipts: true,
|
sendReadReceipts: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (message.get('type') === 'call-history') {
|
||||||
|
const callId = message.get('callId');
|
||||||
|
strictAssert(callId, 'callId not found');
|
||||||
|
dispatch(markCallHistoryReadInConversation(callId));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import type { CallHistoryState } from '../ducks/callHistory';
|
import type { CallHistoryState } from '../ducks/callHistory';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
import {
|
||||||
|
AdhocCallStatus,
|
||||||
|
CallType,
|
||||||
|
type CallHistoryDetails,
|
||||||
|
} from '../../types/CallDisposition';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
|
|
||||||
const getCallHistory = (state: StateType): CallHistoryState =>
|
const getCallHistory = (state: StateType): CallHistoryState =>
|
||||||
|
@ -36,3 +40,28 @@ export const getCallHistoryUnreadCount = createSelector(
|
||||||
return callHistory.unreadCount;
|
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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -152,9 +152,11 @@ import { chunk } from '../util/iterables';
|
||||||
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
import { filterAndClean } from '../types/BodyRange';
|
import { filterAndClean } from '../types/BodyRange';
|
||||||
import { getCallEventForProto } from '../util/callDisposition';
|
import {
|
||||||
|
getCallEventForProto,
|
||||||
|
getCallLogEventForProto,
|
||||||
|
} from '../util/callDisposition';
|
||||||
import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey';
|
import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey';
|
||||||
import { CallLogEvent } from '../types/CallDisposition';
|
|
||||||
import { CallLinkUpdateSyncType } from '../types/CallLink';
|
import { CallLinkUpdateSyncType } from '../types/CallLink';
|
||||||
import { bytesToUuid } from '../util/uuidToBytes';
|
import { bytesToUuid } from '../util/uuidToBytes';
|
||||||
|
|
||||||
|
@ -3614,32 +3616,10 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const { receivedAtCounter } = envelope;
|
const { receivedAtCounter } = envelope;
|
||||||
|
|
||||||
let event: CallLogEvent;
|
const callLogEventDetails = getCallLogEventForProto(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 callLogEventSync = new CallLogEventSyncEvent(
|
const callLogEventSync = new CallLogEventSyncEvent(
|
||||||
{
|
{
|
||||||
event,
|
callLogEventDetails,
|
||||||
timestamp,
|
|
||||||
receivedAtCounter,
|
receivedAtCounter,
|
||||||
},
|
},
|
||||||
this.removeFromCache.bind(this, envelope)
|
this.removeFromCache.bind(this, envelope)
|
||||||
|
|
|
@ -89,13 +89,16 @@ import type {
|
||||||
MessageToDelete,
|
MessageToDelete,
|
||||||
} from './messageReceiverEvents';
|
} from './messageReceiverEvents';
|
||||||
import { getConversationFromTarget } from '../util/deleteForMe';
|
import { getConversationFromTarget } from '../util/deleteForMe';
|
||||||
import type { CallDetails } from '../types/CallDisposition';
|
import type { CallDetails, CallHistoryDetails } from '../types/CallDisposition';
|
||||||
import {
|
import {
|
||||||
AdhocCallStatus,
|
AdhocCallStatus,
|
||||||
DirectCallStatus,
|
DirectCallStatus,
|
||||||
GroupCallStatus,
|
GroupCallStatus,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
import { getProtoForCallHistory } from '../util/callDisposition';
|
import {
|
||||||
|
getBytesForPeerId,
|
||||||
|
getProtoForCallHistory,
|
||||||
|
} from '../util/callDisposition';
|
||||||
import { CallMode } from '../types/Calling';
|
import { CallMode } from '../types/Calling';
|
||||||
import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types';
|
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 ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||||
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
|
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
|
||||||
type: Proto.SyncMessage.CallLogEvent.Type.CLEAR,
|
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();
|
const syncMessage = MessageSender.createSyncMessage();
|
||||||
|
|
|
@ -18,7 +18,10 @@ import type {
|
||||||
ProcessedSent,
|
ProcessedSent,
|
||||||
} from './Types.d';
|
} from './Types.d';
|
||||||
import type { ContactDetailsWithAvatar } from './ContactsParser';
|
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 type { CallLinkUpdateSyncType } from '../types/CallLink';
|
||||||
import { isAciString } from '../util/isAciString';
|
import { isAciString } from '../util/isAciString';
|
||||||
|
|
||||||
|
@ -559,14 +562,13 @@ export class DeleteForMeSyncEvent extends ConfirmableEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CallLogEventSyncEventData = Readonly<{
|
export type CallLogEventSyncEventData = Readonly<{
|
||||||
event: CallLogEvent;
|
callLogEventDetails: CallLogEventDetails;
|
||||||
timestamp: number;
|
|
||||||
receivedAtCounter: number;
|
receivedAtCounter: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export class CallLogEventSyncEvent extends ConfirmableEvent {
|
export class CallLogEventSyncEvent extends ConfirmableEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly callLogEvent: CallLogEventSyncEventData,
|
public readonly data: CallLogEventSyncEventData,
|
||||||
confirm: ConfirmCallback
|
confirm: ConfirmCallback
|
||||||
) {
|
) {
|
||||||
super('callLogEventSync', confirm);
|
super('callLogEventSync', confirm);
|
||||||
|
|
|
@ -26,6 +26,7 @@ export enum CallDirection {
|
||||||
export enum CallLogEvent {
|
export enum CallLogEvent {
|
||||||
Clear = 'Clear',
|
Clear = 'Clear',
|
||||||
MarkedAsRead = 'MarkedAsRead',
|
MarkedAsRead = 'MarkedAsRead',
|
||||||
|
MarkedAsReadInConversation = 'MarkedAsReadInConversation',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LocalCallEvent {
|
export enum LocalCallEvent {
|
||||||
|
@ -97,6 +98,19 @@ export type CallDetails = Readonly<{
|
||||||
timestamp: number;
|
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 &
|
export type CallEventDetails = CallDetails &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
event: CallEvent;
|
event: CallEvent;
|
||||||
|
@ -221,6 +235,13 @@ export const callEventNormalizeSchema = z.object({
|
||||||
event: z.nativeEnum(Proto.SyncMessage.CallEvent.Event),
|
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(
|
export function isSameCallHistoryGroup(
|
||||||
a: CallHistoryGroup,
|
a: CallHistoryGroup,
|
||||||
b: CallHistoryGroup
|
b: CallHistoryGroup
|
||||||
|
|
|
@ -44,6 +44,7 @@ import type {
|
||||||
CallEventDetails,
|
CallEventDetails,
|
||||||
CallHistoryDetails,
|
CallHistoryDetails,
|
||||||
CallHistoryGroup,
|
CallHistoryGroup,
|
||||||
|
CallLogEventDetails,
|
||||||
CallStatus,
|
CallStatus,
|
||||||
GroupCallMeta,
|
GroupCallMeta,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
|
@ -60,6 +61,8 @@ import {
|
||||||
callDetailsSchema,
|
callDetailsSchema,
|
||||||
AdhocCallStatus,
|
AdhocCallStatus,
|
||||||
CallStatusValue,
|
CallStatusValue,
|
||||||
|
callLogEventNormalizeSchema,
|
||||||
|
CallLogEvent,
|
||||||
} from '../types/CallDisposition';
|
} from '../types/CallDisposition';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { ConversationModel } from '../models/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 = {
|
const directionToProto = {
|
||||||
[CallDirection.Incoming]: Proto.SyncMessage.CallEvent.Direction.INCOMING,
|
[CallDirection.Incoming]: Proto.SyncMessage.CallEvent.Direction.INCOMING,
|
||||||
[CallDirection.Outgoing]: Proto.SyncMessage.CallEvent.Direction.OUTGOING,
|
[CallDirection.Outgoing]: Proto.SyncMessage.CallEvent.Direction.OUTGOING,
|
||||||
|
@ -280,6 +311,17 @@ function shouldSyncStatus(callStatus: CallStatus) {
|
||||||
return statusToProto[callStatus] != null;
|
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(
|
export function getProtoForCallHistory(
|
||||||
callHistory: CallHistoryDetails
|
callHistory: CallHistoryDetails
|
||||||
): Proto.SyncMessage.ICallEvent | null {
|
): 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({
|
return new Proto.SyncMessage.CallEvent({
|
||||||
peerId,
|
peerId: getBytesForPeerId(callHistory),
|
||||||
callId: Long.fromString(callHistory.callId),
|
callId: Long.fromString(callHistory.callId),
|
||||||
type: typeToProto[callHistory.type],
|
type: typeToProto[callHistory.type],
|
||||||
direction: directionToProto[callHistory.direction],
|
direction: directionToProto[callHistory.direction],
|
||||||
|
@ -1191,50 +1225,63 @@ export async function updateCallHistoryFromLocalEvent(
|
||||||
await updateRemoteCallHistory(updatedCallHistory);
|
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 {
|
try {
|
||||||
const timestamp = Date.now();
|
log.info(
|
||||||
|
`clearCallHistory: Clearing call history before (${latestCall.callId}, ${latestCall.timestamp})`
|
||||||
log.info(`clearCallHistory: Clearing call history before ${timestamp}`);
|
);
|
||||||
const messageIds = await window.Signal.Data.clearCallHistory(timestamp);
|
const messageIds = await window.Signal.Data.clearCallHistory(latestCall);
|
||||||
|
updateDeletedMessages(messageIds);
|
||||||
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: Queueing sync message');
|
log.info('clearCallHistory: Queueing sync message');
|
||||||
await singleProtoJobQueue.add(
|
await singleProtoJobQueue.add(
|
||||||
MessageSender.getClearCallHistoryMessage(timestamp)
|
MessageSender.getClearCallHistoryMessage(latestCall)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('clearCallHistory: Failed to clear call history', 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 {
|
try {
|
||||||
const timestamp = Date.now();
|
|
||||||
|
|
||||||
log.info(
|
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 ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||||
|
|
||||||
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
|
const callLogEvent = new Proto.SyncMessage.CallLogEvent({
|
||||||
type: Proto.SyncMessage.CallLogEvent.Type.MARKED_AS_READ,
|
type: inConversation
|
||||||
timestamp: Long.fromNumber(timestamp),
|
? 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();
|
const syncMessage = MessageSender.createSyncMessage();
|
||||||
|
|
|
@ -3,39 +3,56 @@
|
||||||
|
|
||||||
import type { CallLogEventSyncEvent } from '../textsecure/messageReceiverEvents';
|
import type { CallLogEventSyncEvent } from '../textsecure/messageReceiverEvents';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
import type { CallLogEventTarget } from '../types/CallDisposition';
|
||||||
import { CallLogEvent } from '../types/CallDisposition';
|
import { CallLogEvent } from '../types/CallDisposition';
|
||||||
import { missingCaseError } from './missingCaseError';
|
import { missingCaseError } from './missingCaseError';
|
||||||
|
import { strictAssert } from './assert';
|
||||||
|
import { updateDeletedMessages } from './callDisposition';
|
||||||
|
|
||||||
export async function onCallLogEventSync(
|
export async function onCallLogEventSync(
|
||||||
syncEvent: CallLogEventSyncEvent
|
syncEvent: CallLogEventSyncEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { callLogEvent, confirm } = syncEvent;
|
const { data, confirm } = syncEvent;
|
||||||
const { event, timestamp } = callLogEvent;
|
const { type, peerId, callId, timestamp } = data.callLogEventDetails;
|
||||||
|
|
||||||
|
const target: CallLogEventTarget = {
|
||||||
|
peerId,
|
||||||
|
callId,
|
||||||
|
timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`onCallLogEventSync: Processing event (Event: ${event}, Timestamp: ${timestamp})`
|
`onCallLogEventSync: Processing event (Event: ${type}, CallId: ${callId}, Timestamp: ${timestamp})`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (event === CallLogEvent.Clear) {
|
if (type === CallLogEvent.Clear) {
|
||||||
log.info(`onCallLogEventSync: Clearing call history before ${timestamp}`);
|
log.info('onCallLogEventSync: Clearing call history');
|
||||||
try {
|
try {
|
||||||
await window.Signal.Data.clearCallHistory(timestamp);
|
const messageIds = await window.Signal.Data.clearCallHistory(target);
|
||||||
|
updateDeletedMessages(messageIds);
|
||||||
} finally {
|
} finally {
|
||||||
// We want to reset the call history even if the clear fails.
|
// We want to reset the call history even if the clear fails.
|
||||||
window.reduxActions.callHistory.resetCallHistory();
|
window.reduxActions.callHistory.resetCallHistory();
|
||||||
}
|
}
|
||||||
confirm();
|
confirm();
|
||||||
} else if (event === CallLogEvent.MarkedAsRead) {
|
} else if (type === CallLogEvent.MarkedAsRead) {
|
||||||
log.info(
|
log.info('onCallLogEventSync: Marking call history read');
|
||||||
`onCallLogEventSync: Marking call history read before ${timestamp}`
|
|
||||||
);
|
|
||||||
try {
|
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 {
|
} finally {
|
||||||
window.reduxActions.callHistory.updateCallHistoryUnreadCount();
|
window.reduxActions.callHistory.updateCallHistoryUnreadCount();
|
||||||
}
|
}
|
||||||
confirm();
|
confirm();
|
||||||
} else {
|
} else {
|
||||||
throw missingCaseError(event);
|
throw missingCaseError(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue