Files
Signal-Desktop/ts/types/CallDisposition.ts
2023-08-21 09:30:33 -07:00

209 lines
5.0 KiB
TypeScript

// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import Long from 'long';
import { CallMode } from './Calling';
import type { AciString } from './ServiceId';
import { aciSchema } from './ServiceId';
import { bytesToUuid } from '../util/uuidToBytes';
import { SignalService as Proto } from '../protobuf';
import * as Bytes from '../Bytes';
export enum CallType {
Audio = 'Audio',
Video = 'Video',
Group = 'Group',
}
export enum CallDirection {
Incoming = 'Incoming',
Outgoing = 'Outgoing',
}
export enum CallLogEvent {
Clear = 'Clear',
}
export enum LocalCallEvent {
Started = 'LocalStarted',
Ringing = 'LocalRinging',
Accepted = 'LocalAccepted',
Declined = 'LocalDeclined',
Hangup = 'LocalHangup', // Incoming = Declined, Outgoing = Missed
RemoteHangup = 'LocalRemoteHangup', // Incoming = Missed, Outgoing = Declined
Missed = 'LocalMissed',
Delete = 'LocalDelete',
}
export enum RemoteCallEvent {
Accepted = 'Accepted',
NotAccepted = 'NotAccepted',
Delete = 'Delete',
}
export type CallEvent = LocalCallEvent | RemoteCallEvent;
export enum DirectCallStatus {
Pending = 'Pending',
Accepted = 'Accepted',
Missed = 'Missed',
Declined = 'Declined',
Deleted = 'Deleted',
}
export enum GroupCallStatus {
GenericGroupCall = 'GenericGroupCall',
OutgoingRing = 'OutgoingRing',
Ringing = 'Ringing',
Joined = 'Joined',
// keep these in sync with direct
Accepted = DirectCallStatus.Accepted,
Missed = DirectCallStatus.Missed,
Declined = DirectCallStatus.Declined,
Deleted = DirectCallStatus.Deleted,
}
export type CallStatus = DirectCallStatus | GroupCallStatus;
export type CallDetails = Readonly<{
callId: string;
peerId: AciString | string;
ringerId: AciString | string | null;
mode: CallMode;
type: CallType;
direction: CallDirection;
timestamp: number;
}>;
export type CallEventDetails = CallDetails &
Readonly<{
event: CallEvent;
}>;
export type CallHistoryDetails = CallDetails &
Readonly<{
status: CallStatus;
}>;
export type CallHistoryGroup = Omit<CallHistoryDetails, 'callId' | 'ringerId'> &
Readonly<{
children: ReadonlyArray<{
callId: string;
timestamp: number;
}>;
}>;
export type GroupCallMeta = Readonly<{
callId: string;
ringerId: string | AciString;
}>;
export enum CallHistoryFilterStatus {
All = 'All',
Missed = 'Missed',
}
export type CallHistoryFilterOptions = Readonly<{
status: CallHistoryFilterStatus;
query: string;
}>;
export type CallHistoryFilter = Readonly<{
status: CallHistoryFilterStatus;
conversationIds: ReadonlyArray<string> | null;
}>;
export type CallHistoryPagination = Readonly<{
offset: number;
limit: number;
}>;
const ringerIdSchema = z.union([aciSchema, z.string(), z.null()]);
const callModeSchema = z.nativeEnum(CallMode);
const callTypeSchema = z.nativeEnum(CallType);
const callDirectionSchema = z.nativeEnum(CallDirection);
const callEventSchema = z.union([
z.nativeEnum(LocalCallEvent),
z.nativeEnum(RemoteCallEvent),
]);
const callStatusSchema = z.union([
z.nativeEnum(DirectCallStatus),
z.nativeEnum(GroupCallStatus),
]);
export const callDetailsSchema = z.object({
callId: z.string(),
peerId: z.string(),
ringerId: ringerIdSchema,
mode: callModeSchema,
type: callTypeSchema,
direction: callDirectionSchema,
timestamp: z.number(),
}) satisfies z.ZodType<CallDetails>;
export const callEventDetailsSchema = callDetailsSchema.extend({
event: callEventSchema,
}) satisfies z.ZodType<CallEventDetails>;
export const callHistoryDetailsSchema = callDetailsSchema.extend({
status: callStatusSchema,
}) satisfies z.ZodType<CallHistoryDetails>;
export const callHistoryGroupSchema = z.object({
peerId: z.string(),
mode: callModeSchema,
type: callTypeSchema,
direction: callDirectionSchema,
status: callStatusSchema,
timestamp: z.number(),
children: z.array(
z.object({
callId: z.string(),
timestamp: z.number(),
})
),
}) satisfies z.ZodType<CallHistoryGroup>;
const peerIdInBytesSchema = z.instanceof(Uint8Array).transform(value => {
const uuid = bytesToUuid(value);
if (uuid != null) {
return uuid;
}
// assuming groupId
return Bytes.toBase64(value);
});
const longToStringSchema = z
.instanceof(Long)
.transform(long => long.toString());
const longToNumberSchema = z
.instanceof(Long)
.transform(long => long.toNumber());
export const callEventNormalizeSchema = z.object({
peerId: peerIdInBytesSchema,
callId: longToStringSchema,
timestamp: longToNumberSchema,
type: z.nativeEnum(Proto.SyncMessage.CallEvent.Type),
direction: z.nativeEnum(Proto.SyncMessage.CallEvent.Direction),
event: z.nativeEnum(Proto.SyncMessage.CallEvent.Event),
});
export function isSameCallHistoryGroup(
a: CallHistoryGroup,
b: CallHistoryGroup
): boolean {
return (
a.peerId === b.peerId &&
a.timestamp === b.timestamp &&
// For a bit more safety.
a.mode === b.mode &&
a.type === b.type &&
a.direction === b.direction &&
a.status === b.status
);
}