signal-desktop/ts/util/callHistoryDetails.ts

114 lines
3.1 KiB
TypeScript

// 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);
}