Call Disposition
This commit is contained in:
parent
9927b132b9
commit
e5638c0b20
20 changed files with 445 additions and 53 deletions
|
@ -988,7 +988,7 @@
|
|||
"description": "Shown in iOS theme when you or someone quotes to a message which is not from you"
|
||||
},
|
||||
"audioPermissionNeeded": {
|
||||
"message": "To send audio messages, allow Signal Desktop to access your microphone.",
|
||||
"message": "To send voice messages, allow Signal Desktop to access your microphone.",
|
||||
"description": "Shown if the user attempts to send an audio message without audio permissions turned on"
|
||||
},
|
||||
"audioCallingPermissionNeeded": {
|
||||
|
@ -1559,7 +1559,7 @@
|
|||
"description": "when you block someone and cannot view their video"
|
||||
},
|
||||
"calling__block-info": {
|
||||
"message": "You won't receive their audio or video and they won't receive yours.",
|
||||
"message": "You won't receive their voice or video and they won't receive yours.",
|
||||
"description": "Shown in the modal dialog to describe how blocking works in a group call"
|
||||
},
|
||||
"calling__overflow__scroll-up": {
|
||||
|
@ -2765,15 +2765,23 @@
|
|||
"description": "Shown in the shortcuts guide"
|
||||
},
|
||||
"Keyboard--accept-video-call": {
|
||||
"message": "Accept call with video",
|
||||
"description": "Shown in the calling keyboard shortcuts guide"
|
||||
"message": "Answer call with video",
|
||||
"description": "(deleted 2023/01/09) Shown in the calling keyboard shortcuts guide"
|
||||
},
|
||||
"Keyboard--accept-audio-call": {
|
||||
"message": "Accept call with audio",
|
||||
"message": "Answer call with audio",
|
||||
"description": "(deleted 2023/01/09) Shown in the calling keyboard shortcuts guide"
|
||||
},
|
||||
"icu:Keyboard--accept-video-call": {
|
||||
"messageformat": "Answer call with video (video calls only)",
|
||||
"description": "Shown in the calling keyboard shortcuts guide"
|
||||
},
|
||||
"icu:Keyboard--accept-call-without-video": {
|
||||
"messageformat": "Answer call without video",
|
||||
"description": "Shown in the calling keyboard shortcuts guide"
|
||||
},
|
||||
"Keyboard--start-audio-call": {
|
||||
"message": "Start audio call",
|
||||
"message": "Start voice call",
|
||||
"description": "Shown in the calling keyboard shortcuts guide"
|
||||
},
|
||||
"Keyboard--start-video-call": {
|
||||
|
@ -3189,11 +3197,11 @@
|
|||
"description": "When a user has no common groups, show this warning"
|
||||
},
|
||||
"acceptCall": {
|
||||
"message": "Answer",
|
||||
"message": "Answer call",
|
||||
"description": "Shown in tooltip for the button to accept a call (audio or video)"
|
||||
},
|
||||
"acceptCallWithoutVideo": {
|
||||
"message": "Answer without video",
|
||||
"message": "Answer call without video",
|
||||
"description": "Shown in tooltip for the button to accept a video call without video"
|
||||
},
|
||||
"declineCall": {
|
||||
|
@ -3201,40 +3209,40 @@
|
|||
"description": "Shown in tooltip for the button to decline a call (audio or video)"
|
||||
},
|
||||
"declinedIncomingAudioCall": {
|
||||
"message": "You declined an audio call",
|
||||
"description": "Shown in conversation history when you declined an incoming audio call"
|
||||
"message": "You declined a voice call",
|
||||
"description": "Shown in conversation history when you declined an incoming voice call"
|
||||
},
|
||||
"declinedIncomingVideoCall": {
|
||||
"message": "You declined a video call",
|
||||
"description": "Shown in conversation history when you declined an incoming video call"
|
||||
},
|
||||
"acceptedIncomingAudioCall": {
|
||||
"message": "Incoming audio call",
|
||||
"description": "Shown in conversation history when you accepted an incoming audio call"
|
||||
"message": "Incoming voice call",
|
||||
"description": "Shown in conversation history when you accepted an incoming voice call"
|
||||
},
|
||||
"acceptedIncomingVideoCall": {
|
||||
"message": "Incoming video call",
|
||||
"description": "Shown in conversation history when you accepted an incoming video call"
|
||||
},
|
||||
"missedIncomingAudioCall": {
|
||||
"message": "Missed audio call",
|
||||
"description": "Shown in conversation history when you missed an incoming audio call"
|
||||
"message": "Missed voice call",
|
||||
"description": "Shown in conversation history when you missed an incoming voice call"
|
||||
},
|
||||
"missedIncomingVideoCall": {
|
||||
"message": "Missed video call",
|
||||
"description": "Shown in conversation history when you missed an incoming video call"
|
||||
},
|
||||
"acceptedOutgoingAudioCall": {
|
||||
"message": "Outgoing audio call",
|
||||
"description": "Shown in conversation history when you made an outgoing audio call"
|
||||
"message": "Outgoing voice call",
|
||||
"description": "Shown in conversation history when you made an outgoing voice call"
|
||||
},
|
||||
"acceptedOutgoingVideoCall": {
|
||||
"message": "Outgoing video call",
|
||||
"description": "Shown in conversation history when you made an outgoing video call"
|
||||
},
|
||||
"missedOrDeclinedOutgoingAudioCall": {
|
||||
"message": "Unanswered audio call",
|
||||
"description": "Shown in conversation history when your audio call is missed or declined"
|
||||
"message": "Unanswered voice call",
|
||||
"description": "Shown in conversation history when your voice call is missed or declined"
|
||||
},
|
||||
"missedOrDeclinedOutgoingVideoCall": {
|
||||
"message": "Unanswered video call",
|
||||
|
@ -3249,8 +3257,8 @@
|
|||
"description": "Shown in a notification body when Signal is minimized to tray"
|
||||
},
|
||||
"incomingAudioCall": {
|
||||
"message": "Incoming audio call...",
|
||||
"description": "Shown in both the incoming call bar and notification for an incoming audio call"
|
||||
"message": "Incoming voice call...",
|
||||
"description": "Shown in both the incoming call bar and notification for an incoming voice call"
|
||||
},
|
||||
"incomingVideoCall": {
|
||||
"message": "Incoming video call...",
|
||||
|
|
|
@ -573,6 +573,33 @@ message SyncMessage {
|
|||
optional uint32 registrationId = 3;
|
||||
}
|
||||
|
||||
message CallEvent {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
AUDIO_CALL = 1;
|
||||
VIDEO_CALL = 2;
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
UNKNOWN = 0;
|
||||
INCOMING = 1;
|
||||
OUTGOING = 2;
|
||||
}
|
||||
|
||||
enum Event {
|
||||
UNKNOWN = 0;
|
||||
ACCEPTED = 1;
|
||||
NOT_ACCEPTED = 2;
|
||||
}
|
||||
|
||||
optional bytes peerUuid = 1;
|
||||
optional uint64 callId = 2;
|
||||
optional uint64 timestamp = 3;
|
||||
optional Type type = 4;
|
||||
optional Direction direction = 5;
|
||||
optional Event event = 6;
|
||||
}
|
||||
|
||||
optional Sent sent = 1;
|
||||
optional Contacts contacts = 2;
|
||||
optional Groups groups = 3;
|
||||
|
@ -591,6 +618,7 @@ message SyncMessage {
|
|||
repeated Viewed viewed = 16;
|
||||
optional PniIdentity pniIdentity = 17;
|
||||
optional PniChangeNumber pniChangeNumber = 18;
|
||||
optional CallEvent callEvent = 19;
|
||||
}
|
||||
|
||||
message AttachmentPointer {
|
||||
|
|
|
@ -161,6 +161,7 @@ import { clearConversationDraftAttachments } from './util/clearConversationDraft
|
|||
import { removeLinkPreview } from './services/LinkPreview';
|
||||
import { PanelType } from './types/Panels';
|
||||
import { getQuotedMessageSelector } from './state/selectors/composer';
|
||||
import { onCallEventSync } from './util/onCallEventSync';
|
||||
|
||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||
|
||||
|
@ -411,6 +412,10 @@ export async function startApp(): Promise<void> {
|
|||
'storyRecipientUpdate',
|
||||
queuedEventListener(onStoryRecipientUpdate, false)
|
||||
);
|
||||
messageReceiver.addEventListener(
|
||||
'callEventSync',
|
||||
queuedEventListener(onCallEventSync, false)
|
||||
);
|
||||
});
|
||||
|
||||
ourProfileKeyService.initialize(window.storage);
|
||||
|
|
|
@ -224,8 +224,10 @@ export function IncomingCallBar(props: PropsType): JSX.Element | null {
|
|||
}, [bounceAppIconStart, bounceAppIconStop]);
|
||||
|
||||
const acceptVideoCall = useCallback(() => {
|
||||
if (isVideoCall) {
|
||||
acceptCall({ conversationId, asVideoCall: true });
|
||||
}, [acceptCall, conversationId]);
|
||||
}
|
||||
}, [isVideoCall, acceptCall, conversationId]);
|
||||
|
||||
const acceptAudioCall = useCallback(() => {
|
||||
acceptCall({ conversationId, asVideoCall: false });
|
||||
|
|
|
@ -205,11 +205,11 @@ const CALLING_SHORTCUTS: Array<ShortcutType> = [
|
|||
keys: [['shift', 'V']],
|
||||
},
|
||||
{
|
||||
description: 'Keyboard--accept-video-call',
|
||||
description: 'icu:Keyboard--accept-video-call',
|
||||
keys: [['ctrlOrAlt', 'shift', 'V']],
|
||||
},
|
||||
{
|
||||
description: 'Keyboard--accept-audio-call',
|
||||
description: 'icu:Keyboard--accept-call-without-video',
|
||||
keys: [['ctrlOrAlt', 'shift', 'A']],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { Tooltip, TooltipPlacement } from '../Tooltip';
|
||||
import * as log from '../../logging/log';
|
||||
import { assertDev } from '../../util/assert';
|
||||
|
||||
export type PropsActionsType = {
|
||||
returnToActiveCall: () => void;
|
||||
|
@ -42,11 +43,14 @@ export const CallingNotification: React.FC<PropsType> = React.memo(
|
|||
let timestamp: number;
|
||||
let wasMissed = false;
|
||||
switch (props.callMode) {
|
||||
case CallMode.Direct:
|
||||
timestamp = props.acceptedTime || props.endedTime;
|
||||
case CallMode.Direct: {
|
||||
const resolvedTime = props.acceptedTime ?? props.endedTime;
|
||||
assertDev(resolvedTime, 'Direct call must have accepted or ended time');
|
||||
timestamp = resolvedTime;
|
||||
wasMissed =
|
||||
props.wasIncoming && !props.acceptedTime && !props.wasDeclined;
|
||||
break;
|
||||
}
|
||||
case CallMode.Group:
|
||||
timestamp = props.startedTime;
|
||||
break;
|
||||
|
|
|
@ -60,7 +60,7 @@ import type {
|
|||
} from '../types/Colors';
|
||||
import type { MessageModel } from './messages';
|
||||
import { getContact } from '../messages/helpers';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
import { isConversationMuted } from '../util/isConversationMuted';
|
||||
import { isConversationSMSOnly } from '../util/isConversationSMSOnly';
|
||||
import {
|
||||
|
@ -3226,8 +3226,11 @@ export class ConversationModel extends window.Backbone
|
|||
let detailsToSave: CallHistoryDetailsType;
|
||||
|
||||
switch (callHistoryDetails.callMode) {
|
||||
case CallMode.Direct:
|
||||
timestamp = callHistoryDetails.endedTime;
|
||||
case CallMode.Direct: {
|
||||
const resolvedTime =
|
||||
callHistoryDetails.acceptedTime ?? callHistoryDetails.endedTime;
|
||||
assertDev(resolvedTime, 'Direct call must have accepted or ended time');
|
||||
timestamp = resolvedTime;
|
||||
unread =
|
||||
!callHistoryDetails.wasDeclined && !callHistoryDetails.acceptedTime;
|
||||
detailsToSave = {
|
||||
|
@ -3235,6 +3238,7 @@ export class ConversationModel extends window.Backbone
|
|||
callMode: CallMode.Direct,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case CallMode.Group:
|
||||
timestamp = callHistoryDetails.startedTime;
|
||||
unread = false;
|
||||
|
@ -3257,9 +3261,20 @@ export class ConversationModel extends window.Backbone
|
|||
// TODO: DESKTOP-722
|
||||
} as unknown as MessageAttributesType;
|
||||
|
||||
if (callHistoryDetails.callMode === CallMode.Direct) {
|
||||
const messageId = await window.Signal.Data.getCallHistoryMessageByCallId(
|
||||
this.id,
|
||||
callHistoryDetails.callId
|
||||
);
|
||||
if (messageId != null) {
|
||||
message.id = messageId;
|
||||
}
|
||||
}
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
|
||||
const model = window.MessageController.register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
BandwidthMode,
|
||||
} from '@signalapp/ringrtc';
|
||||
import { uniqBy, noop } from 'lodash';
|
||||
import Long from 'long';
|
||||
|
||||
import type {
|
||||
ActionsType as CallingReduxActionsType,
|
||||
|
@ -1549,11 +1550,15 @@ export class CallingClass {
|
|||
|
||||
await this.handleOutgoingSignaling(remoteUserId, message);
|
||||
|
||||
const callId = callingMessage.offer.callId?.toString();
|
||||
assertDev(callId != null, 'Call ID missing from offer');
|
||||
|
||||
const ProtoOfferType = Proto.CallingMessage.Offer.Type;
|
||||
this.addCallHistoryForFailedIncomingCall(
|
||||
await this.addCallHistoryForFailedIncomingCall(
|
||||
conversation,
|
||||
callingMessage.offer.type === ProtoOfferType.OFFER_VIDEO_CALL,
|
||||
envelope.timestamp
|
||||
envelope.timestamp,
|
||||
callId
|
||||
);
|
||||
|
||||
return;
|
||||
|
@ -1847,6 +1852,7 @@ export class CallingClass {
|
|||
return null;
|
||||
}
|
||||
|
||||
const callId = Long.fromValue(call.callId).toString();
|
||||
try {
|
||||
// The peer must be 'trusted' before accepting a call from them.
|
||||
// This is mostly the safety number check, unverified meaning that they were
|
||||
|
@ -1859,10 +1865,11 @@ export class CallingClass {
|
|||
log.info(
|
||||
`Peer is not trusted, ignoring incoming call for conversation: ${conversation.idForLogging()}`
|
||||
);
|
||||
this.addCallHistoryForFailedIncomingCall(
|
||||
await this.addCallHistoryForFailedIncomingCall(
|
||||
conversation,
|
||||
call.isVideoCall,
|
||||
Date.now()
|
||||
Date.now(),
|
||||
callId
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
@ -1879,17 +1886,18 @@ export class CallingClass {
|
|||
return await this.getCallSettings(conversation);
|
||||
} catch (err) {
|
||||
log.error(`Ignoring incoming call: ${Errors.toLogFormat(err)}`);
|
||||
this.addCallHistoryForFailedIncomingCall(
|
||||
await this.addCallHistoryForFailedIncomingCall(
|
||||
conversation,
|
||||
call.isVideoCall,
|
||||
Date.now()
|
||||
Date.now(),
|
||||
callId
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private handleAutoEndedIncomingCallRequest(
|
||||
_callId: CallId,
|
||||
private async handleAutoEndedIncomingCallRequest(
|
||||
callId: CallId,
|
||||
remoteUserId: UserId,
|
||||
reason: CallEndedReason,
|
||||
ageInSeconds: number,
|
||||
|
@ -1909,12 +1917,13 @@ export class CallingClass {
|
|||
: 0;
|
||||
const endedTime = Date.now() - ageInMilliseconds;
|
||||
|
||||
this.addCallHistoryForAutoEndedIncomingCall(
|
||||
await this.addCallHistoryForAutoEndedIncomingCall(
|
||||
conversation,
|
||||
reason,
|
||||
endedTime,
|
||||
wasVideoCall,
|
||||
receivedAtCounter
|
||||
receivedAtCounter,
|
||||
Long.fromValue(callId).toString()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1929,16 +1938,18 @@ export class CallingClass {
|
|||
let acceptedTime: number | undefined;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
call.handleStateChanged = () => {
|
||||
call.handleStateChanged = async () => {
|
||||
if (call.state === CallState.Accepted) {
|
||||
acceptedTime = acceptedTime || Date.now();
|
||||
} else if (call.state === CallState.Ended) {
|
||||
this.addCallHistoryForEndedCall(conversation, call, acceptedTime);
|
||||
await this.addCallHistoryForEndedCall(conversation, call, acceptedTime);
|
||||
this.stopDeviceReselectionTimer();
|
||||
this.lastMediaDeviceSettings = undefined;
|
||||
delete this.callsByConversation[conversation.id];
|
||||
}
|
||||
reduxInterface.callStateChange({
|
||||
remoteUserId: call.remoteUserId,
|
||||
callId: Long.fromValue(call.callId).toString(),
|
||||
conversationId: conversation.id,
|
||||
acceptedTime,
|
||||
callState: call.state,
|
||||
|
@ -2088,7 +2099,7 @@ export class CallingClass {
|
|||
};
|
||||
}
|
||||
|
||||
private addCallHistoryForEndedCall(
|
||||
private async addCallHistoryForEndedCall(
|
||||
conversation: ConversationModel,
|
||||
call: Call,
|
||||
acceptedTimeParam: number | undefined
|
||||
|
@ -2110,8 +2121,11 @@ export class CallingClass {
|
|||
acceptedTime = Date.now();
|
||||
}
|
||||
|
||||
void conversation.addCallHistory(
|
||||
const callId = Long.fromValue(call.callId).toString();
|
||||
|
||||
await conversation.addCallHistory(
|
||||
{
|
||||
callId,
|
||||
callMode: CallMode.Direct,
|
||||
wasIncoming: call.isIncoming,
|
||||
wasVideoCall: call.isVideoCall,
|
||||
|
@ -2123,12 +2137,13 @@ export class CallingClass {
|
|||
);
|
||||
}
|
||||
|
||||
private addCallHistoryForFailedIncomingCall(
|
||||
private async addCallHistoryForFailedIncomingCall(
|
||||
conversation: ConversationModel,
|
||||
wasVideoCall: boolean,
|
||||
timestamp: number
|
||||
timestamp: number,
|
||||
callId: string
|
||||
) {
|
||||
void conversation.addCallHistory(
|
||||
await conversation.addCallHistory(
|
||||
{
|
||||
callMode: CallMode.Direct,
|
||||
wasIncoming: true,
|
||||
|
@ -2137,17 +2152,19 @@ export class CallingClass {
|
|||
wasDeclined: false,
|
||||
acceptedTime: undefined,
|
||||
endedTime: timestamp,
|
||||
callId,
|
||||
},
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
private addCallHistoryForAutoEndedIncomingCall(
|
||||
private async addCallHistoryForAutoEndedIncomingCall(
|
||||
conversation: ConversationModel,
|
||||
reason: CallEndedReason,
|
||||
endedTime: number,
|
||||
wasVideoCall: boolean,
|
||||
receivedAtCounter: number | undefined
|
||||
receivedAtCounter: number | undefined,
|
||||
callId: string
|
||||
) {
|
||||
let wasDeclined = false;
|
||||
let acceptedTime;
|
||||
|
@ -2159,8 +2176,9 @@ export class CallingClass {
|
|||
}
|
||||
// Otherwise it will show up as a missed call.
|
||||
|
||||
void conversation.addCallHistory(
|
||||
await conversation.addCallHistory(
|
||||
{
|
||||
callId,
|
||||
callMode: CallMode.Direct,
|
||||
wasIncoming: true,
|
||||
wasVideoCall,
|
||||
|
|
|
@ -546,6 +546,10 @@ export type DataInterface = {
|
|||
getLastConversationMessage(options: {
|
||||
conversationId: string;
|
||||
}): Promise<MessageType | undefined>;
|
||||
getCallHistoryMessageByCallId(
|
||||
conversationId: string,
|
||||
callId: string
|
||||
): Promise<string | void>;
|
||||
hasGroupCallHistoryMessage: (
|
||||
conversationId: string,
|
||||
eraId: string
|
||||
|
|
|
@ -256,6 +256,7 @@ const dataInterface: ServerInterface = {
|
|||
getConversationRangeCenteredOnMessage,
|
||||
getConversationMessageStats,
|
||||
getLastConversationMessage,
|
||||
getCallHistoryMessageByCallId,
|
||||
hasGroupCallHistoryMessage,
|
||||
migrateConversationMessages,
|
||||
|
||||
|
@ -3009,6 +3010,32 @@ async function getConversationRangeCenteredOnMessage({
|
|||
})();
|
||||
}
|
||||
|
||||
async function getCallHistoryMessageByCallId(
|
||||
conversationId: string,
|
||||
callId: string
|
||||
): Promise<string | void> {
|
||||
const db = getInstance();
|
||||
|
||||
const id: string | void = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
SELECT id
|
||||
FROM messages
|
||||
WHERE conversationId = $conversationId
|
||||
AND type = 'call-history'
|
||||
AND callMode = 'Direct'
|
||||
AND callId = $callId
|
||||
`
|
||||
)
|
||||
.pluck()
|
||||
.get({
|
||||
conversationId,
|
||||
callId,
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async function hasGroupCallHistoryMessage(
|
||||
conversationId: string,
|
||||
eraId: string
|
||||
|
|
38
ts/sql/migrations/72-optimize-call-id-message-lookup.ts
Normal file
38
ts/sql/migrations/72-optimize-call-id-message-lookup.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
|
||||
export default function updateToSchemaVersion72(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 72) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction(() => {
|
||||
db.exec(
|
||||
`
|
||||
ALTER TABLE messages
|
||||
ADD COLUMN callId TEXT
|
||||
GENERATED ALWAYS AS (
|
||||
json_extract(json, '$.callHistoryDetails.callId')
|
||||
);
|
||||
ALTER TABLE messages
|
||||
ADD COLUMN callMode TEXT
|
||||
GENERATED ALWAYS AS (
|
||||
json_extract(json, '$.callHistoryDetails.callMode')
|
||||
);
|
||||
CREATE INDEX messages_call ON messages
|
||||
(conversationId, type, callMode, callId);
|
||||
`
|
||||
);
|
||||
|
||||
db.pragma('user_version = 72');
|
||||
})();
|
||||
|
||||
logger.info('updateToSchemaVersion72: success!');
|
||||
}
|
|
@ -47,6 +47,7 @@ import updateToSchemaVersion68 from './68-drop-deprecated-columns';
|
|||
import updateToSchemaVersion69 from './69-group-call-ring-cancellations';
|
||||
import updateToSchemaVersion70 from './70-story-reply-index';
|
||||
import updateToSchemaVersion71 from './71-merge-notifications';
|
||||
import updateToSchemaVersion72 from './72-optimize-call-id-message-lookup';
|
||||
|
||||
function updateToSchemaVersion1(
|
||||
currentVersion: number,
|
||||
|
@ -1963,6 +1964,7 @@ export const SCHEMA_VERSIONS = [
|
|||
|
||||
updateToSchemaVersion70,
|
||||
updateToSchemaVersion71,
|
||||
updateToSchemaVersion72,
|
||||
];
|
||||
|
||||
export function updateSchema(db: Database, logger: LoggerType): void {
|
||||
|
|
|
@ -52,6 +52,8 @@ import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
|||
import { SHOW_TOAST } from './toast';
|
||||
import { ToastType } from '../../types/Toast';
|
||||
import type { ShowToastActionType } from './toast';
|
||||
import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue';
|
||||
import MessageSender from '../../textsecure/SendMessage';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -137,6 +139,8 @@ export type AcceptCallType = {
|
|||
};
|
||||
|
||||
export type CallStateChangeType = {
|
||||
remoteUserId: string; // TODO: Remove
|
||||
callId: string; // TODO: Remove
|
||||
conversationId: string;
|
||||
acceptedTime?: number;
|
||||
callState: CallState;
|
||||
|
@ -688,12 +692,67 @@ function callStateChange(
|
|||
CallStateChangeFulfilledActionType
|
||||
> {
|
||||
return async dispatch => {
|
||||
const { callState } = payload;
|
||||
const {
|
||||
callId,
|
||||
callState,
|
||||
isVideoCall,
|
||||
isIncoming,
|
||||
acceptedTime,
|
||||
callEndedReason,
|
||||
remoteUserId,
|
||||
} = payload;
|
||||
|
||||
if (callState === CallState.Ended) {
|
||||
await callingTones.playEndCall();
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
}
|
||||
|
||||
const isOutgoing = !isIncoming;
|
||||
const wasAccepted = acceptedTime != null;
|
||||
const isConnected = callState === CallState.Accepted; // "connected"
|
||||
const isEnded = callState === CallState.Ended && callEndedReason != null;
|
||||
|
||||
const isLocalHangup = callEndedReason === CallEndedReason.LocalHangup;
|
||||
const isRemoteHangup = callEndedReason === CallEndedReason.RemoteHangup;
|
||||
|
||||
const answered = isConnected && wasAccepted;
|
||||
const notAnswered = isEnded && !wasAccepted;
|
||||
|
||||
const isOutgoingRemoteAccept = isOutgoing && isConnected && answered;
|
||||
const isIncomingLocalAccept = isIncoming && isConnected && answered;
|
||||
const isOutgoingLocalHangup = isOutgoing && isLocalHangup && notAnswered;
|
||||
const isIncomingLocalHangup = isIncoming && isLocalHangup && notAnswered;
|
||||
const isOutgoingRemoteHangup = isOutgoing && isRemoteHangup && notAnswered;
|
||||
const isIncomingRemoteHangup = isIncoming && isRemoteHangup && notAnswered;
|
||||
|
||||
if (isIncomingRemoteHangup) {
|
||||
// This is considered just another "missed" event
|
||||
log.info('callStateChange: not syncing hangup from self');
|
||||
} else if (
|
||||
isOutgoingRemoteAccept ||
|
||||
isIncomingLocalAccept ||
|
||||
isOutgoingLocalHangup ||
|
||||
isIncomingLocalHangup ||
|
||||
isOutgoingRemoteHangup
|
||||
) {
|
||||
try {
|
||||
await singleProtoJobQueue.add(
|
||||
MessageSender.getCallEventSync(
|
||||
remoteUserId,
|
||||
callId,
|
||||
isVideoCall,
|
||||
isIncoming,
|
||||
acceptedTime != null
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'callStateChange: Failed to queue sync message',
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CALL_STATE_CHANGE_FULFILLED,
|
||||
payload,
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
SignedPreKeys,
|
||||
} from '../LibSignalStores';
|
||||
import { verifySignature } from '../Curve';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
import type { BatcherType } from '../util/batcher';
|
||||
import { createBatcher } from '../util/batcher';
|
||||
import { drop } from '../util/drop';
|
||||
|
@ -111,6 +111,7 @@ import {
|
|||
GroupEvent,
|
||||
GroupSyncEvent,
|
||||
StoryRecipientUpdateEvent,
|
||||
CallEventSyncEvent,
|
||||
} from './messageReceiverEvents';
|
||||
import * as log from '../logging/log';
|
||||
import * as durations from '../util/durations';
|
||||
|
@ -617,6 +618,11 @@ export default class MessageReceiver
|
|||
handler: (ev: StoryRecipientUpdateEvent) => void
|
||||
): void;
|
||||
|
||||
public override addEventListener(
|
||||
name: 'callEventSync',
|
||||
handler: (ev: CallEventSyncEvent) => void
|
||||
): void;
|
||||
|
||||
public override addEventListener(name: string, handler: EventHandler): void {
|
||||
return super.addEventListener(name, handler);
|
||||
}
|
||||
|
@ -2958,6 +2964,9 @@ export default class MessageReceiver
|
|||
if (syncMessage.viewed && syncMessage.viewed.length) {
|
||||
return this.handleViewed(envelope, syncMessage.viewed);
|
||||
}
|
||||
if (syncMessage.callEvent) {
|
||||
return this.handleCallEvent(envelope, syncMessage.callEvent);
|
||||
}
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
log.warn(
|
||||
|
@ -3219,6 +3228,64 @@ export default class MessageReceiver
|
|||
);
|
||||
}
|
||||
|
||||
private async handleCallEvent(
|
||||
envelope: ProcessedEnvelope,
|
||||
callEvent: Proto.SyncMessage.ICallEvent
|
||||
): Promise<void> {
|
||||
const logId = getEnvelopeId(envelope);
|
||||
log.info('MessageReceiver.handleCallEvent', logId);
|
||||
const { peerUuid, callId } = callEvent;
|
||||
|
||||
if (!peerUuid) {
|
||||
throw new Error('MessageReceiver.handleCallEvent: missing peerUuid');
|
||||
}
|
||||
|
||||
if (!callId) {
|
||||
throw new Error('MessageReceiver.handleCallEvent: missing callId');
|
||||
}
|
||||
|
||||
logUnexpectedUrgentValue(envelope, 'callEventSync');
|
||||
|
||||
const peerUuidStr = bytesToUuid(peerUuid);
|
||||
|
||||
assertDev(
|
||||
peerUuidStr != null,
|
||||
'MessageReceiver.handleCallEvent: invalid peerUuid'
|
||||
);
|
||||
|
||||
const { receivedAtCounter, timestamp } = envelope;
|
||||
|
||||
const wasIncoming =
|
||||
callEvent.direction === Proto.SyncMessage.CallEvent.Direction.INCOMING;
|
||||
const wasVideoCall =
|
||||
callEvent.type === Proto.SyncMessage.CallEvent.Type.VIDEO_CALL;
|
||||
const wasAccepted =
|
||||
callEvent.event === Proto.SyncMessage.CallEvent.Event.ACCEPTED;
|
||||
const wasDeclined =
|
||||
callEvent.event === Proto.SyncMessage.CallEvent.Event.NOT_ACCEPTED;
|
||||
|
||||
const acceptedTime = wasAccepted ? timestamp : undefined;
|
||||
const endedTime = wasDeclined ? timestamp : undefined;
|
||||
|
||||
const callEventSync = new CallEventSyncEvent(
|
||||
{
|
||||
timestamp: envelope.timestamp,
|
||||
peerUuid: peerUuidStr,
|
||||
callId: callId.toString(),
|
||||
wasIncoming,
|
||||
wasVideoCall,
|
||||
wasDeclined,
|
||||
acceptedTime,
|
||||
endedTime,
|
||||
receivedAtCounter,
|
||||
},
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
);
|
||||
await this.dispatchAndWait(logId, callEventSync);
|
||||
|
||||
log.info('handleCallEvent: finished');
|
||||
}
|
||||
|
||||
private async handleContacts(
|
||||
envelope: ProcessedEnvelope,
|
||||
contacts: Proto.SyncMessage.IContacts
|
||||
|
|
|
@ -1897,6 +1897,52 @@ export default class MessageSender {
|
|||
};
|
||||
}
|
||||
|
||||
static getCallEventSync(
|
||||
peerUuid: string,
|
||||
callId: string,
|
||||
isVideoCall: boolean,
|
||||
isIncoming: boolean,
|
||||
isAccepted: boolean
|
||||
): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
|
||||
const type = isVideoCall
|
||||
? Proto.SyncMessage.CallEvent.Type.VIDEO_CALL
|
||||
: Proto.SyncMessage.CallEvent.Type.AUDIO_CALL;
|
||||
const direction = isIncoming
|
||||
? Proto.SyncMessage.CallEvent.Direction.INCOMING
|
||||
: Proto.SyncMessage.CallEvent.Direction.OUTGOING;
|
||||
const event = isAccepted
|
||||
? Proto.SyncMessage.CallEvent.Event.ACCEPTED
|
||||
: Proto.SyncMessage.CallEvent.Event.NOT_ACCEPTED;
|
||||
|
||||
syncMessage.callEvent = new Proto.SyncMessage.CallEvent({
|
||||
peerUuid: uuidToBytes(peerUuid),
|
||||
callId: Long.fromString(callId),
|
||||
type,
|
||||
direction,
|
||||
event,
|
||||
timestamp: Long.fromNumber(Date.now()),
|
||||
});
|
||||
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
),
|
||||
type: 'callEventSync',
|
||||
urgent: false,
|
||||
};
|
||||
}
|
||||
|
||||
static getVerificationSync(
|
||||
destinationE164: string | undefined,
|
||||
destinationUuid: string | undefined,
|
||||
|
|
|
@ -404,6 +404,27 @@ export class ViewSyncEvent extends ConfirmableEvent {
|
|||
}
|
||||
}
|
||||
|
||||
export type CallEventSyncEventData = Readonly<{
|
||||
timestamp: number;
|
||||
peerUuid: string;
|
||||
callId: string;
|
||||
wasVideoCall: boolean;
|
||||
wasIncoming: boolean;
|
||||
wasDeclined: boolean;
|
||||
acceptedTime: number | undefined;
|
||||
endedTime: number | undefined;
|
||||
receivedAtCounter: number;
|
||||
}>;
|
||||
|
||||
export class CallEventSyncEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly callEvent: CallEventSyncEventData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('callEventSync', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type StoryRecipientUpdateData = Readonly<{
|
||||
destinationUuid: string;
|
||||
storyMessageRecipients: Array<Proto.SyncMessage.Sent.IStoryMessageRecipient>;
|
||||
|
|
|
@ -167,12 +167,13 @@ export type MediaDeviceSettings = AvailableIODevicesType & {
|
|||
};
|
||||
|
||||
type DirectCallHistoryDetailsType = {
|
||||
callId: string;
|
||||
callMode: CallMode.Direct;
|
||||
wasIncoming: boolean;
|
||||
wasVideoCall: boolean;
|
||||
wasDeclined: boolean;
|
||||
acceptedTime?: number;
|
||||
endedTime: number;
|
||||
endedTime?: number;
|
||||
};
|
||||
|
||||
type GroupCallHistoryDetailsType = {
|
||||
|
|
|
@ -13,7 +13,7 @@ type DirectCallNotificationType = {
|
|||
wasVideoCall: boolean;
|
||||
wasDeclined: boolean;
|
||||
acceptedTime?: number;
|
||||
endedTime: number;
|
||||
endedTime?: number;
|
||||
};
|
||||
|
||||
type GroupCallNotificationType = {
|
||||
|
|
|
@ -65,6 +65,7 @@ export const sendTypesEnum = z.enum([
|
|||
'verificationSync',
|
||||
'viewOnceSync',
|
||||
'viewSync',
|
||||
'callEventSync',
|
||||
|
||||
// No longer used, all non-urgent
|
||||
'legacyGroupChange',
|
||||
|
|
46
ts/util/onCallEventSync.ts
Normal file
46
ts/util/onCallEventSync.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { CallEventSyncEvent } from '../textsecure/messageReceiverEvents';
|
||||
import * as log from '../logging/log';
|
||||
import { CallMode } from '../types/Calling';
|
||||
|
||||
export async function onCallEventSync(
|
||||
syncEvent: CallEventSyncEvent
|
||||
): Promise<void> {
|
||||
const { callEvent, confirm } = syncEvent;
|
||||
const {
|
||||
peerUuid,
|
||||
callId,
|
||||
wasIncoming,
|
||||
wasVideoCall,
|
||||
wasDeclined,
|
||||
acceptedTime,
|
||||
endedTime,
|
||||
receivedAtCounter,
|
||||
} = callEvent;
|
||||
|
||||
const conversation = window.ConversationController.get(peerUuid);
|
||||
|
||||
if (!conversation) {
|
||||
log.warn(`onCallEventSync: No conversation found for peerUuid ${peerUuid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await conversation.queueJob('onCallEventSync', async () => {
|
||||
await conversation.addCallHistory(
|
||||
{
|
||||
callId,
|
||||
callMode: CallMode.Direct,
|
||||
wasDeclined,
|
||||
wasIncoming,
|
||||
wasVideoCall,
|
||||
acceptedTime: acceptedTime ?? undefined,
|
||||
endedTime: endedTime ?? undefined,
|
||||
},
|
||||
receivedAtCounter
|
||||
);
|
||||
|
||||
confirm();
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue