Validate and log transitions for call disposition
This commit is contained in:
parent
e2f39ed5fa
commit
688ddd49d1
2 changed files with 143 additions and 14 deletions
|
@ -160,6 +160,7 @@ import { stripNewlinesForLeftPane } from '../util/stripNewlinesForLeftPane';
|
|||
import { findAndFormatContact } from '../util/findAndFormatContact';
|
||||
import { deriveProfileKeyVersion } from '../util/zkgroup';
|
||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||
import { validateTransition } from '../util/callHistoryDetails';
|
||||
|
||||
const EMPTY_ARRAY: Readonly<[]> = [];
|
||||
const EMPTY_GROUP_COLLISIONS: GroupNameCollisionsWithIdsByTitle = {};
|
||||
|
@ -3396,21 +3397,9 @@ export class ConversationModel extends window.Backbone
|
|||
// awaited it would block on this forever.
|
||||
drop(
|
||||
this.queueJob('addCallHistory', async () => {
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
conversationId: this.id,
|
||||
type: 'call-history',
|
||||
sent_at: timestamp,
|
||||
timestamp,
|
||||
received_at: receivedAtCounter || incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
readStatus: unread ? ReadStatus.Unread : ReadStatus.Read,
|
||||
seenStatus: unread ? SeenStatus.Unseen : SeenStatus.NotApplicable,
|
||||
callHistoryDetails: detailsToSave,
|
||||
};
|
||||
|
||||
// Force save if we're adding a new call history message for a direct call
|
||||
let forceSave = true;
|
||||
let previousMessage: MessageAttributesType | void;
|
||||
if (callHistoryDetails.callMode === CallMode.Direct) {
|
||||
const messageId =
|
||||
await window.Signal.Data.getCallHistoryMessageByCallId(
|
||||
|
@ -3421,9 +3410,11 @@ export class ConversationModel extends window.Backbone
|
|||
log.info(
|
||||
`addCallHistory: Found existing call history message (Call ID: ${callHistoryDetails.callId}, Message ID: ${messageId})`
|
||||
);
|
||||
message.id = messageId;
|
||||
// We don't want to force save if we're updating an existing message
|
||||
forceSave = false;
|
||||
previousMessage = await window.Signal.Data.getMessageById(
|
||||
messageId
|
||||
);
|
||||
} else {
|
||||
log.info(
|
||||
`addCallHistory: No existing call history message found (Call ID: ${callHistoryDetails.callId})`
|
||||
|
@ -3431,6 +3422,30 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!validateTransition(
|
||||
previousMessage?.callHistoryDetails,
|
||||
callHistoryDetails,
|
||||
log
|
||||
)
|
||||
) {
|
||||
log.info("addCallHistory: Transition isn't valid, not saving");
|
||||
return;
|
||||
}
|
||||
|
||||
const message: MessageAttributesType = {
|
||||
id: previousMessage?.id ?? generateGuid(),
|
||||
conversationId: this.id,
|
||||
type: 'call-history',
|
||||
sent_at: timestamp,
|
||||
timestamp,
|
||||
received_at: receivedAtCounter || incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
readStatus: unread ? ReadStatus.Unread : ReadStatus.Read,
|
||||
seenStatus: unread ? SeenStatus.Unseen : SeenStatus.NotApplicable,
|
||||
callHistoryDetails,
|
||||
};
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
forceSave,
|
||||
|
|
114
ts/util/callHistoryDetails.ts
Normal file
114
ts/util/callHistoryDetails.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { CallHistoryDetailsFromDiskType } from '../types/Calling';
|
||||
import { CallMode } from '../types/Calling';
|
||||
import type { LoggerType } from '../types/Logging';
|
||||
import { strictAssert } from './assert';
|
||||
import { missingCaseError } from './missingCaseError';
|
||||
|
||||
enum CallHistoryStatus {
|
||||
Pending = 'Pending',
|
||||
Missed = 'Missed',
|
||||
Accepted = 'Accepted',
|
||||
NotAccepted = 'NotAccepted',
|
||||
}
|
||||
|
||||
function getCallHistoryStatus(
|
||||
callHistoryDetails: CallHistoryDetailsFromDiskType
|
||||
): CallHistoryStatus {
|
||||
strictAssert(
|
||||
callHistoryDetails.callMode === CallMode.Direct,
|
||||
"Can't get call history status for group call (unimplemented)"
|
||||
);
|
||||
if (callHistoryDetails.acceptedTime != null) {
|
||||
return CallHistoryStatus.Accepted;
|
||||
}
|
||||
if (callHistoryDetails.wasDeclined) {
|
||||
return CallHistoryStatus.NotAccepted;
|
||||
}
|
||||
if (callHistoryDetails.endedTime != null) {
|
||||
return CallHistoryStatus.Missed;
|
||||
}
|
||||
return CallHistoryStatus.Pending;
|
||||
}
|
||||
|
||||
function isAllowedTransition(
|
||||
from: CallHistoryStatus,
|
||||
to: CallHistoryStatus,
|
||||
log: LoggerType
|
||||
): boolean {
|
||||
if (from === CallHistoryStatus.Pending) {
|
||||
log.info('callHistoryDetails: Can go from pending to anything.');
|
||||
return true;
|
||||
}
|
||||
if (to === CallHistoryStatus.Pending) {
|
||||
log.info("callHistoryDetails: Can't go to pending once out of it.");
|
||||
return false;
|
||||
}
|
||||
if (from === CallHistoryStatus.Missed) {
|
||||
log.info(
|
||||
"callHistoryDetails: A missed call on this device might've been picked up or explicitly declined on a linked device."
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (from === CallHistoryStatus.Accepted) {
|
||||
log.info(
|
||||
'callHistoryDetails: If we accept anywhere that beats everything.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
from === CallHistoryStatus.NotAccepted &&
|
||||
to === CallHistoryStatus.Accepted
|
||||
) {
|
||||
log.info(
|
||||
'callHistoryDetails: If we declined on this device but picked up on another device, that counts as accepted.'
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (from === CallHistoryStatus.NotAccepted) {
|
||||
log.info(
|
||||
"callHistoryDetails: Can't transition from NotAccepted to anything else"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw missingCaseError(from);
|
||||
}
|
||||
|
||||
export function validateTransition(
|
||||
prev: CallHistoryDetailsFromDiskType | void,
|
||||
next: CallHistoryDetailsFromDiskType,
|
||||
log: LoggerType
|
||||
): boolean {
|
||||
// Only validating Direct calls for now
|
||||
if (next.callMode !== CallMode.Direct) {
|
||||
return true;
|
||||
}
|
||||
if (prev == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
strictAssert(
|
||||
prev.callMode === CallMode.Direct && next.callMode === CallMode.Direct,
|
||||
"Call mode must be 'Direct'"
|
||||
);
|
||||
strictAssert(prev.callId === next.callId, 'Call ID must not change');
|
||||
strictAssert(
|
||||
prev.wasIncoming === next.wasIncoming,
|
||||
'wasIncoming must not change'
|
||||
);
|
||||
strictAssert(
|
||||
prev.wasVideoCall === next.wasVideoCall,
|
||||
'wasVideoCall must not change'
|
||||
);
|
||||
|
||||
const before = getCallHistoryStatus(prev);
|
||||
const after = getCallHistoryStatus(next);
|
||||
log.info(
|
||||
`callHistoryDetails: Checking transition (Call ID: ${next.callId}, Before: ${before}, After: ${after})`
|
||||
);
|
||||
return isAllowedTransition(before, after, log);
|
||||
}
|
Loading…
Reference in a new issue