fix: make callMessage forced to be DaR for recipient

and put workaround for our local message
pull/2940/head
Audric Ackermann 6 months ago
parent 9e0a984297
commit 286260fae8

@ -70,7 +70,7 @@ const handleAcceptConversationRequest = async (convoId: string) => {
await convo.setIsApproved(true, false);
await convo.commit();
await convo.addOutgoingApprovalMessage(Date.now());
await approveConvoAndSendResponse(convoId, true);
await approveConvoAndSendResponse(convoId);
};
export const ConversationMessageRequestButtons = () => {

@ -49,10 +49,10 @@ import {
updateUserDetailsModal,
} from '../../state/ducks/modalDialog';
import { getIsMessageSection } from '../../state/selectors/section';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
/** Menu items standardized */
@ -487,7 +487,7 @@ export const AcceptMsgRequestMenuItem = () => {
onClick={async () => {
await convo.setDidApproveMe(true);
await convo.addOutgoingApprovalMessage(Date.now());
await approveConvoAndSendResponse(convoId, true);
await approveConvoAndSendResponse(convoId);
}}
>
{window.i18n('accept')}

@ -102,10 +102,7 @@ export async function unblockConvoById(conversationId: string) {
/**
* marks the conversation's approval fields, sends messageRequestResponse, syncs to linked devices
*/
export const approveConvoAndSendResponse = async (
conversationId: string,
syncToDevices: boolean = true
) => {
export const approveConvoAndSendResponse = async (conversationId: string) => {
const convoToApprove = getConversationController().get(conversationId);
if (!convoToApprove) {
@ -117,11 +114,6 @@ export const approveConvoAndSendResponse = async (
await convoToApprove.commit();
await convoToApprove.sendMessageRequestResponse();
// Conversation was not approved before so a sync is needed
if (syncToDevices) {
await forceSyncConfigurationNowIfNeeded();
}
};
export async function declineConversationWithoutConfirm({

@ -1,26 +1,30 @@
import _ from 'lodash';
import { toNumber } from 'lodash';
import { SignalService } from '../protobuf';
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { TTL_DEFAULT } from '../session/constants';
import { CallManager, UserUtils } from '../session/utils';
import { WithMessageHash, WithOptExpireUpdate } from '../session/utils/calling/CallManager';
import { removeFromCache } from './cache';
import { EnvelopePlus } from './types';
// messageHash & messageHash are only needed for actions adding a callMessage to the database (so they expire)
export async function handleCallMessage(
envelope: EnvelopePlus,
callMessage: SignalService.CallMessage
callMessage: SignalService.CallMessage,
expireDetails: WithOptExpireUpdate & WithMessageHash
) {
const { Type } = SignalService.CallMessage;
const sender = envelope.senderIdentity || envelope.source;
const sentTimestamp = _.toNumber(envelope.timestamp);
const sentTimestamp = toNumber(envelope.timestamp);
const { type } = callMessage;
// we just allow self send of ANSWER message to remove the incoming call dialog when we accepted it from another device
// we just allow self send of ANSWER/END_CALL message to remove the incoming call dialog when we accepted it from another device
if (
sender === UserUtils.getOurPubKeyStrFromCache() &&
callMessage.type !== SignalService.CallMessage.Type.ANSWER &&
callMessage.type !== SignalService.CallMessage.Type.END_CALL
callMessage.type !== Type.ANSWER &&
callMessage.type !== Type.END_CALL
) {
window.log.info('Dropping incoming call from ourself');
await removeFromCache(envelope);
@ -34,21 +38,12 @@ export async function handleCallMessage(
return;
}
if (type === SignalService.CallMessage.Type.PROVISIONAL_ANSWER) {
if (type === Type.PROVISIONAL_ANSWER || type === Type.PRE_OFFER) {
await removeFromCache(envelope);
window.log.info('Skipping callMessage PROVISIONAL_ANSWER');
return;
}
if (type === SignalService.CallMessage.Type.PRE_OFFER) {
await removeFromCache(envelope);
window.log.info('Skipping callMessage PRE_OFFER');
return;
}
if (type === SignalService.CallMessage.Type.OFFER) {
if (type === Type.OFFER) {
if (
Math.max(sentTimestamp - GetNetworkTime.getNowWithNetworkOffset()) > TTL_DEFAULT.CALL_MESSAGE
) {
@ -59,7 +54,7 @@ export async function handleCallMessage(
}
await removeFromCache(envelope);
await CallManager.handleCallTypeOffer(sender, callMessage, sentTimestamp);
await CallManager.handleCallTypeOffer(sender, callMessage, sentTimestamp, expireDetails);
return;
}
@ -75,7 +70,7 @@ export async function handleCallMessage(
if (type === SignalService.CallMessage.Type.ANSWER) {
await removeFromCache(envelope);
await CallManager.handleCallTypeAnswer(sender, callMessage, sentTimestamp);
await CallManager.handleCallTypeAnswer(sender, callMessage, sentTimestamp, expireDetails);
return;
}

@ -15,7 +15,6 @@ import {
} from '../interactions/conversations/unsendingInteractions';
import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../models/conversationAttributes';
import { findCachedBlindedMatchOrLookupOnAllServers } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { getConversationController } from '../session/conversations';
import { concatUInt8Array, getSodiumRenderer } from '../session/crypto';
import { removeMessagePadding } from '../session/crypto/BufferPadding';
@ -548,26 +547,10 @@ export async function innerHandleSwarmContentMessage({
if (content.dataExtractionNotification) {
perfStart(`handleDataExtractionNotification-${envelope.id}`);
// DataExtractionNotification uses the expiration setting of our side of the 1o1 conversation. whatever we get in the contentMessage
const expirationTimer = senderConversationModel.getExpireTimer();
const expirationType = DisappearingMessages.changeToDisappearingMessageType(
senderConversationModel,
expirationTimer,
senderConversationModel.getExpirationMode()
);
await handleDataExtractionNotification({
envelope,
dataExtractionNotification: content.dataExtractionNotification as SignalService.DataExtractionNotification,
expireUpdate: {
expirationTimer,
expirationType,
messageExpirationFromRetrieve:
expirationType === 'unknown'
? null
: GetNetworkTime.getNowWithNetworkOffset() + expirationTimer * 1000,
},
expireUpdate,
messageHash,
});
perfEnd(
@ -580,7 +563,10 @@ export async function innerHandleSwarmContentMessage({
await handleUnsendMessage(envelope, content.unsendMessage as SignalService.Unsend);
}
if (content.callMessage) {
await handleCallMessage(envelope, content.callMessage as SignalService.CallMessage);
await handleCallMessage(envelope, content.callMessage as SignalService.CallMessage, {
expireDetails: expireUpdate,
messageHash,
});
}
if (content.messageRequestResponse) {
await handleMessageRequestResponse(
@ -862,7 +848,7 @@ export async function handleDataExtractionNotification({
}: {
envelope: EnvelopePlus;
dataExtractionNotification: SignalService.DataExtractionNotification;
expireUpdate: ReadyToDisappearMsgUpdate;
expireUpdate: ReadyToDisappearMsgUpdate | undefined;
messageHash: string;
}): Promise<void> {
// we currently don't care about the timestamp included in the field itself, just the timestamp of the envelope

@ -271,6 +271,58 @@ function changeToDisappearingMessageType(
return 'unknown';
}
// TODO legacy messages support will be removed in a future release
/**
* Forces a private DaS to be a DaR.
* This should only be used for DataExtractionNotification and CallMessages (the ones saved to the DB) currently.
* Note: this can only be called for private conversations, excluding ourselves as it throws otherwise (this wouldn't be right)
* */
function forcedDeleteAfterReadMsgSetting(
convo: ConversationModel
): { expirationType: Exclude<DisappearingMessageType, 'deleteAfterSend'>; expireTimer: number } {
if (convo.isMe() || !convo.isPrivate()) {
throw new Error(
'forcedDeleteAfterReadMsgSetting can only be called with a private chat (excluding ourselves)'
);
}
const expirationMode = convo.getExpirationMode();
const expireTimer = convo.getExpireTimer();
if (expirationMode === 'off' || expirationMode === 'legacy' || expireTimer <= 0) {
return { expirationType: 'unknown', expireTimer: 0 };
}
return {
expirationType: expirationMode === 'deleteAfterSend' ? 'deleteAfterRead' : expirationMode,
expireTimer,
};
}
// TODO legacy messages support will be removed in a future release
/**
* Forces a private DaR to be a DaS.
* This should only be used for the outgoing CallMessages that we keep locally only (not synced, just the "you started a call" notification)
* Note: this can only be called for private conversations, excluding ourselves as it throws otherwise (this wouldn't be right)
* */
function forcedDeleteAfterSendMsgSetting(
convo: ConversationModel
): { expirationType: Exclude<DisappearingMessageType, 'deleteAfterRead'>; expireTimer: number } {
if (convo.isMe() || !convo.isPrivate()) {
throw new Error(
'forcedDeleteAfterSendMsgSetting can only be called with a private chat (excluding ourselves)'
);
}
const expirationMode = convo.getExpirationMode();
const expireTimer = convo.getExpireTimer();
if (expirationMode === 'off' || expirationMode === 'legacy' || expireTimer <= 0) {
return { expirationType: 'unknown', expireTimer: 0 };
}
return {
expirationType: expirationMode === 'deleteAfterRead' ? 'deleteAfterSend' : expirationMode,
expireTimer,
};
}
// TODO legacy messages support will be removed in a future release
/**
* Converts DisappearingMessageType to DisappearingMessageConversationModeType
@ -509,7 +561,6 @@ function getMessageReadyToDisappear(
messageExpirationFromRetrieve &&
messageExpirationFromRetrieve > 0
) {
const expirationStartTimestamp = messageExpirationFromRetrieve - expireTimer * 1000;
const expires_at = messageExpirationFromRetrieve;
messageModel.set({
@ -633,6 +684,8 @@ export const DisappearingMessages = {
setExpirationStartTimestamp,
changeToDisappearingMessageType,
changeToDisappearingConversationMode,
forcedDeleteAfterReadMsgSetting,
forcedDeleteAfterSendMsgSetting,
checkForExpireUpdateInContentMessage,
checkForExpiringOutgoingMessage,
getMessageReadyToDisappear,

@ -40,9 +40,9 @@ export class CallMessage extends ExpirableMessage {
}
public contentProto(): SignalService.Content {
return new SignalService.Content({
callMessage: this.dataCallProto(),
});
const content = super.contentProto();
content.callMessage = this.dataCallProto();
return content;
}
public ttl() {

@ -2,6 +2,7 @@ import { getMessageQueue } from '../../..';
import { SignalService } from '../../../../protobuf';
import { SnodeNamespaces } from '../../../apis/snode_api/namespaces';
import { getConversationController } from '../../../conversations';
import { DisappearingMessages } from '../../../disappearing_messages';
import { PubKey } from '../../../types';
import { UserUtils } from '../../../utils';
import { ExpirableMessage, ExpirableMessageParams } from '../ExpirableMessage';
@ -23,15 +24,15 @@ export class DataExtractionNotificationMessage extends ExpirableMessage {
}
public contentProto(): SignalService.Content {
return new SignalService.Content({
dataExtractionNotification: this.dataExtractionProto(),
});
const content = super.contentProto();
content.dataExtractionNotification = this.dataExtractionProto();
return content;
}
protected dataExtractionProto(): SignalService.DataExtractionNotification {
const ACTION_ENUM = SignalService.DataExtractionNotification.Type;
const action = ACTION_ENUM.MEDIA_SAVED; // we cannot know when user screenshots, so it can only be a media saved
const action = ACTION_ENUM.MEDIA_SAVED; // we cannot know when user screenshots, so it can only be a media saved on desktop
return new SignalService.DataExtractionNotification({
type: action,
@ -53,13 +54,17 @@ export const sendDataExtractionNotification = async (
window.log.warn('Not sending saving attachment notification for', attachmentSender);
return;
}
// DataExtractionNotification are expiring with the recipient, so don't include ours
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
// DataExtractionNotification are expiring with a forced DaR timer if a DaS is set.
// It's because we want the DataExtractionNotification to stay in the swarm as much as possible,
// but also expire on the recipient's side (and synced) once read.
const dataExtractionNotificationMessage = new DataExtractionNotificationMessage({
referencedAttachmentTimestamp,
timestamp: Date.now(),
expirationType: null,
expireTimer: null,
expirationType,
expireTimer,
});
const pubkey = PubKey.cast(conversationId);

@ -28,18 +28,16 @@ export abstract class ClosedGroupMessage extends ExpirableMessage {
}
public contentProto(): SignalService.Content {
return new SignalService.Content({
dataMessage: this.dataProto(),
...super.contentProto(),
// TODO legacy messages support will be removed in a future release
// Closed Groups only support 'deleteAfterSend' and 'legacy'
expirationType:
this.expirationType === 'deleteAfterSend'
? SignalService.Content.ExpirationType.DELETE_AFTER_SEND
: this.expirationType
? SignalService.Content.ExpirationType.UNKNOWN
: undefined,
});
const content = super.contentProto();
content.dataMessage = this.dataProto();
// TODO legacy messages support will be removed in a future release
// Closed Groups only support 'deleteAfterSend' and 'legacy'
content.expirationType =
this.expirationType === 'deleteAfterSend'
? SignalService.Content.ExpirationType.DELETE_AFTER_SEND
: SignalService.Content.ExpirationType.UNKNOWN;
return content;
}
public dataProto(): SignalService.DataMessage {

@ -107,10 +107,9 @@ export class VisibleMessage extends ExpirableMessage {
}
public contentProto(): SignalService.Content {
return new SignalService.Content({
...super.contentProto(),
dataMessage: this.dataProto(),
});
const content = super.contentProto();
content.dataMessage = this.dataProto();
return content;
}
public dataProto(): SignalService.DataMessage {

@ -31,6 +31,7 @@ import { GetNetworkTime } from '../../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../../apis/snode_api/namespaces';
import { DURATION } from '../../constants';
import { DisappearingMessages } from '../../disappearing_messages';
import { ReadyToDisappearMsgUpdate } from '../../disappearing_messages/types';
import { MessageSender } from '../../sending';
import { getIsRinging } from '../RingingManager';
import { getBlackSilenceMediaStream } from './Silence';
@ -39,6 +40,9 @@ export type InputItem = { deviceId: string; label: string };
export const callTimeoutMs = 60000;
export type WithOptExpireUpdate = { expireDetails: ReadyToDisappearMsgUpdate | undefined };
export type WithMessageHash = { messageHash: string };
/**
* This uuid is set only once we accepted a call or started one.
*/
@ -108,6 +112,9 @@ type CachedCallMessageType = {
sdpMids: Array<string>;
uuid: string;
timestamp: number;
// when we receive some messages, we keep track of what were their
// expireUpdate, so we can add a message once the user / accepts denies the call
expireDetails: (WithOptExpireUpdate & WithMessageHash) | null;
};
/**
@ -385,8 +392,12 @@ export async function selectAudioOutputByDeviceId(audioOutputDeviceId: string) {
}
}
async function createOfferAndSendIt(recipient: string) {
async function createOfferAndSendIt(recipient: string, msgIdentifier: string | null) {
try {
const convo = getConversationController().get(recipient);
if (!convo) {
throw new Error('createOfferAndSendIt needs a convo');
}
makingOffer = true;
window.log.info('got createOfferAndSendIt event. creating offer');
await (peerConnection as any)?.setLocalDescription();
@ -412,13 +423,19 @@ async function createOfferAndSendIt(recipient: string) {
''
);
// Note: we are forcing callMessages to be DaR if DaS, using the same timer
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
const offerMessage = new CallMessage({
identifier: msgIdentifier || undefined,
timestamp: Date.now(),
type: SignalService.CallMessage.Type.OFFER,
sdps: [overridenSdps],
uuid: currentCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expireTimer: null,
expirationType,
expireTimer,
});
window.log.info(`sending '${offer.type}'' with callUUID: ${currentCallUUID}`);
@ -505,7 +522,7 @@ export async function USER_callRecipient(recipient: string) {
timestamp: now,
type: SignalService.CallMessage.Type.PRE_OFFER,
uuid: currentCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expirationType: null, // Note: Preoffer messages are not added to the DB, so no need to make them expire
expireTimer: null,
});
@ -515,43 +532,8 @@ export async function USER_callRecipient(recipient: string) {
await calledConvo.unhideIfNeeded(false);
weAreCallerOnCurrentCall = true;
const expirationMode = calledConvo.getExpirationMode();
const expireTimer = calledConvo.getExpireTimer() || 0;
let expirationType;
let expirationStartTimestamp;
if (calledConvo && expirationMode && expireTimer > 0) {
// TODO legacy messages support will be removed in a future release
expirationType = DisappearingMessages.changeToDisappearingMessageType(
calledConvo,
expireTimer,
expirationMode
);
if (
expirationMode === 'legacy' ||
expirationMode === 'deleteAfterSend' ||
expirationMode === 'deleteAfterRead' // we are the one initiaing the call, so that message is already read
) {
expirationStartTimestamp = DisappearingMessages.setExpirationStartTimestamp(
expirationMode,
now,
'USER_callRecipient'
);
}
}
// Locally, that message must expire
await calledConvo?.addSingleOutgoingMessage({
callNotificationType: 'started-call',
sent_at: now,
expirationType,
expireTimer,
expirationStartTimestamp,
});
// initiating a call is analogous to sending a message request
await approveConvoAndSendResponse(recipient, true);
await approveConvoAndSendResponse(recipient);
// Note: we do the sending of the preoffer manually as the sendToPubkeyNonDurably rely on having a message saved to the db for MessageSentSuccess
// which is not the case for a pre offer message (the message only exists in memory)
@ -567,7 +549,25 @@ export async function USER_callRecipient(recipient: string) {
void PnServer.notifyPnServer(wrappedEnvelope, recipient);
await openMediaDevicesAndAddTracks();
await createOfferAndSendIt(recipient);
// Note CallMessages are very custom, as we moslty don't sync them to ourselves.
// So here, we are creating a DaS/off message saved locally which will expire locally only,
// but the "offer" we are sending the the called pubkey had a DaR on it (as that one is synced, and should expire after our message was read)
const expireDetails = DisappearingMessages.forcedDeleteAfterSendMsgSetting(calledConvo);
let msgModel = await calledConvo?.addSingleOutgoingMessage({
callNotificationType: 'started-call',
sent_at: now,
expirationType: expireDetails.expirationType,
expireTimer: expireDetails.expireTimer,
});
msgModel = DisappearingMessages.getMessageReadyToDisappear(calledConvo, msgModel, 0, {
messageExpirationFromRetrieve: null,
expirationTimer: expireDetails.expireTimer,
expirationType: expireDetails.expirationType,
});
const msgIdentifier = await msgModel.commit();
await createOfferAndSendIt(recipient, msgIdentifier);
// close and end the call if callTimeoutMs is reached and still not connected
// eslint-disable-next-line @typescript-eslint/no-misused-promises
@ -615,7 +615,7 @@ const iceSenderDebouncer = _.debounce(async (recipient: string) => {
sdpMids: validCandidates.map(c => c.sdpMid),
sdps: validCandidates.map(c => c.candidate),
uuid: currentCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expirationType: null, // Note: An ICE_CANDIDATES is not saved to the DB on the recipient's side, so no need to make it expire
expireTimer: null,
});
@ -819,7 +819,7 @@ function createOrGetPeerConnection(withPubkey: string) {
// we are the caller and the connection got dropped out, we need to send a new offer with iceRestart set to true.
// the recipient will get that new offer and send us a response back if he still online
(peerConnection as any).restartIce();
await createOfferAndSendIt(withPubkey);
await createOfferAndSendIt(withPubkey, null);
}
}, 2000);
}
@ -914,58 +914,47 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
callerConvo.set('active_at', networkTimestamp);
await callerConvo.unhideIfNeeded(false);
const expirationMode = callerConvo.getExpirationMode();
const expireTimer = callerConvo.getExpireTimer() || 0;
let expirationType;
let expirationStartTimestamp;
const expireUpdate = DisappearingMessages.forcedDeleteAfterSendMsgSetting(callerConvo);
if (callerConvo && expirationMode && expireTimer > 0) {
// TODO legacy messages support will be removed in a future release
expirationType = DisappearingMessages.changeToDisappearingMessageType(
callerConvo,
expireTimer,
expirationMode
);
if (
expirationMode === 'legacy' ||
expirationMode === 'deleteAfterSend' ||
expirationMode === 'deleteAfterRead'
) {
expirationStartTimestamp = DisappearingMessages.setExpirationStartTimestamp(
expirationMode,
networkTimestamp,
'USER_acceptIncomingCallRequest'
);
}
}
await callerConvo?.addSingleIncomingMessage({
const msgModel = await callerConvo.addSingleIncomingMessage({
callNotificationType: 'answered-a-call',
source: UserUtils.getOurPubKeyStrFromCache(),
sent_at: networkTimestamp,
received_at: networkTimestamp,
unread: READ_MESSAGE_STATE.read,
expirationType,
expireTimer,
expirationStartTimestamp,
messageHash: lastOfferMessage.expireDetails?.messageHash,
expirationType: expireUpdate.expirationType,
expireTimer: expireUpdate.expireTimer,
});
await buildAnswerAndSendIt(fromSender);
const msgIdentifier = await msgModel.commit();
await buildAnswerAndSendIt(fromSender, msgIdentifier);
// consider the conversation completely approved
await callerConvo.setDidApproveMe(true);
await approveConvoAndSendResponse(fromSender, true);
await approveConvoAndSendResponse(fromSender);
}
export async function rejectCallAlreadyAnotherCall(fromSender: string, forcedUUID: string) {
const convo = getConversationController().get(fromSender);
if (!convo) {
throw new Error('rejectCallAlreadyAnotherCall non existing convo');
}
window.log.info(`rejectCallAlreadyAnotherCall ${ed25519Str(fromSender)}: ${forcedUUID}`);
rejectedCallUUIDS.add(forcedUUID);
// Note: we are forcing callMessages to be DaR if DaS, using the same timer
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
const rejectCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
uuid: forcedUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expireTimer: null,
expirationType,
expireTimer,
});
await sendCallMessageAndSync(rejectCallMessage, fromSender);
@ -985,12 +974,21 @@ export async function USER_rejectIncomingCallRequest(fromSender: string) {
window.log.info(`USER_rejectIncomingCallRequest ${ed25519Str(fromSender)}: ${aboutCallUUID}`);
if (aboutCallUUID) {
rejectedCallUUIDS.add(aboutCallUUID);
const convo = getConversationController().get(fromSender);
if (!convo) {
throw new Error('USER_rejectIncomingCallRequest not existing convo');
}
// Note: we are forcing callMessages to be DaR if DaS, using the same timer
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
const endCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
uuid: aboutCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expireTimer: null,
expirationType,
expireTimer,
});
// sync the reject event so our other devices remove the popup too
await sendCallMessageAndSync(endCallMessage, fromSender);
@ -1003,7 +1001,7 @@ export async function USER_rejectIncomingCallRequest(fromSender: string) {
if (ongoingCallWith && ongoingCallStatus && ongoingCallWith === fromSender) {
closeVideoCall();
}
await addMissedCallMessage(fromSender, Date.now());
await addMissedCallMessage(fromSender, Date.now(), lastOfferMessage?.expireDetails || null);
}
async function sendCallMessageAndSync(callmessage: CallMessage, user: string) {
@ -1028,13 +1026,21 @@ export async function USER_hangup(fromSender: string) {
window.log.warn('should not be able to hangup without a currentCallUUID');
return;
}
const convo = getConversationController().get(fromSender);
if (!convo) {
throw new Error('USER_hangup not existing convo');
}
// Note: we are forcing callMessages to be DaR if DaS, using the same timer
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
rejectedCallUUIDS.add(currentCallUUID);
const endCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
uuid: currentCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expireTimer: null,
expirationType,
expireTimer,
});
void getMessageQueue().sendToPubKeyNonDurably({
pubkey: PubKey.cast(fromSender),
@ -1093,7 +1099,7 @@ export async function handleCallTypeEndCall(sender: string, aboutCallUUID?: stri
}
}
async function buildAnswerAndSendIt(sender: string) {
async function buildAnswerAndSendIt(sender: string, msgIdentifier: string | null) {
if (peerConnection) {
if (!currentCallUUID) {
window.log.warn('cannot send answer without a currentCallUUID');
@ -1105,14 +1111,23 @@ async function buildAnswerAndSendIt(sender: string) {
window.log.warn('failed to create answer');
return;
}
const convo = getConversationController().get(sender);
if (!convo) {
throw new Error('buildAnswerAndSendIt not existing convo');
}
// Note: we are forcing callMessages to be DaR if DaS, using the same timer
const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting(
convo
);
const answerSdp = answer.sdp;
const callAnswerMessage = new CallMessage({
identifier: msgIdentifier || undefined,
timestamp: Date.now(),
type: SignalService.CallMessage.Type.ANSWER,
sdps: [answerSdp],
uuid: currentCallUUID,
expirationType: null, // Note: CallMessages are expiring based on the recipient's side expiration setting
expireTimer: null,
expirationType,
expireTimer,
});
window.log.info('sending ANSWER MESSAGE and sync');
@ -1126,8 +1141,9 @@ export function isCallRejected(uuid: string) {
function getCachedMessageFromCallMessage(
callMessage: SignalService.CallMessage,
envelopeTimestamp: number
) {
envelopeTimestamp: number,
expireDetails: (WithOptExpireUpdate & WithMessageHash) | null
): CachedCallMessageType {
return {
type: callMessage.type,
sdps: callMessage.sdps,
@ -1135,6 +1151,7 @@ function getCachedMessageFromCallMessage(
sdpMids: callMessage.sdpMids,
uuid: callMessage.uuid,
timestamp: envelopeTimestamp,
expireDetails,
};
}
@ -1153,7 +1170,8 @@ async function isUserApprovedOrWeSentAMessage(user: string) {
export async function handleCallTypeOffer(
sender: string,
callMessage: SignalService.CallMessage,
incomingOfferTimestamp: number
incomingOfferTimestamp: number,
details: WithMessageHash & WithOptExpireUpdate
) {
try {
const remoteCallUUID = callMessage.uuid;
@ -1164,25 +1182,33 @@ export async function handleCallTypeOffer(
if (!getCallMediaPermissionsSettings()) {
// we still add it to the cache so if user toggles settings in the next 60 sec, he can still reply to it
const cachedMsg = getCachedMessageFromCallMessage(callMessage, incomingOfferTimestamp);
const cachedMsg = getCachedMessageFromCallMessage(
callMessage,
incomingOfferTimestamp,
details
);
pushCallMessageToCallCache(sender, remoteCallUUID, cachedMsg);
await handleMissedCall(sender, incomingOfferTimestamp, 'permissions');
await handleMissedCall(sender, incomingOfferTimestamp, 'permissions', details);
return;
}
const shouldDisplayOffer = await isUserApprovedOrWeSentAMessage(sender);
if (!shouldDisplayOffer) {
const cachedMsg = getCachedMessageFromCallMessage(callMessage, incomingOfferTimestamp);
const cachedMsg = getCachedMessageFromCallMessage(
callMessage,
incomingOfferTimestamp,
details
);
pushCallMessageToCallCache(sender, remoteCallUUID, cachedMsg);
await handleMissedCall(sender, incomingOfferTimestamp, 'not-approved');
await handleMissedCall(sender, incomingOfferTimestamp, 'not-approved', details);
return;
}
// if the offer is more than the call timeout, don't try to handle it (as the sender would have already closed it)
if (incomingOfferTimestamp <= Date.now() - callTimeoutMs) {
await handleMissedCall(sender, incomingOfferTimestamp, 'too-old-timestamp');
await handleMissedCall(sender, incomingOfferTimestamp, 'too-old-timestamp', details);
return;
}
@ -1195,7 +1221,7 @@ export async function handleCallTypeOffer(
return;
}
// add a message in the convo with this user about the missed call.
await handleMissedCall(sender, incomingOfferTimestamp, 'another-call-ongoing');
await handleMissedCall(sender, incomingOfferTimestamp, 'another-call-ongoing', details);
// Here, we are in a call, and we got an offer from someone we are in a call with, and not one of his other devices.
// Just hangup automatically the call on the calling side.
@ -1227,7 +1253,7 @@ export async function handleCallTypeOffer(
await peerConnection.setRemoteDescription(remoteOfferDesc); // SRD rolls back as needed
isSettingRemoteAnswerPending = false;
await buildAnswerAndSendIt(sender);
await buildAnswerAndSendIt(sender, null);
} else {
window.inboxStore?.dispatch(incomingCall({ pubkey: sender }));
@ -1240,7 +1266,11 @@ export async function handleCallTypeOffer(
await callerConvo.notifyIncomingCall();
}
}
const cachedMessage = getCachedMessageFromCallMessage(callMessage, incomingOfferTimestamp);
const cachedMessage = getCachedMessageFromCallMessage(
callMessage,
incomingOfferTimestamp,
details
);
pushCallMessageToCallCache(sender, remoteCallUUID, cachedMessage);
} catch (err) {
@ -1251,7 +1281,8 @@ export async function handleCallTypeOffer(
export async function handleMissedCall(
sender: string,
incomingOfferTimestamp: number,
reason: 'not-approved' | 'permissions' | 'another-call-ongoing' | 'too-old-timestamp'
reason: 'not-approved' | 'permissions' | 'another-call-ongoing' | 'too-old-timestamp',
details: WithMessageHash & WithOptExpireUpdate
) {
const incomingCallConversation = getConversationController().get(sender);
@ -1276,10 +1307,14 @@ export async function handleMissedCall(
default:
}
await addMissedCallMessage(sender, incomingOfferTimestamp);
await addMissedCallMessage(sender, incomingOfferTimestamp, details);
}
async function addMissedCallMessage(callerPubkey: string, sentAt: number) {
async function addMissedCallMessage(
callerPubkey: string,
sentAt: number,
details: (WithMessageHash & WithOptExpireUpdate) | null
) {
const incomingCallConversation = getConversationController().get(callerPubkey);
if (incomingCallConversation.isActive() || incomingCallConversation.isHidden()) {
@ -1287,44 +1322,25 @@ async function addMissedCallMessage(callerPubkey: string, sentAt: number) {
await incomingCallConversation.unhideIfNeeded(false);
}
// Note: Missed call messages are expiring with our side of the conversation settings.
const expirationMode = incomingCallConversation.getExpirationMode();
const expireTimer = incomingCallConversation.getExpireTimer() || 0;
let expirationType;
let expirationStartTimestamp;
// Note: Missed call messages should be sent with DaR setting or off. Don't enforce it here.
// if it's set to something, apply it to the missed message we are creating
if (incomingCallConversation && expirationMode && expireTimer > 0) {
// TODO legacy messages support will be removed in a future release
expirationType = DisappearingMessages.changeToDisappearingMessageType(
incomingCallConversation,
expireTimer,
expirationMode
);
if (
expirationMode === 'legacy' ||
expirationMode === 'deleteAfterSend' ||
expirationMode === 'deleteAfterRead'
) {
expirationStartTimestamp = DisappearingMessages.setExpirationStartTimestamp(
expirationMode,
sentAt,
'addMissedCallMessage'
);
}
}
await incomingCallConversation?.addSingleIncomingMessage({
let msgModel = await incomingCallConversation?.addSingleIncomingMessage({
callNotificationType: 'missed-call',
source: callerPubkey,
sent_at: sentAt,
received_at: GetNetworkTime.getNowWithNetworkOffset(),
unread: READ_MESSAGE_STATE.unread,
expirationType,
expireTimer,
expirationStartTimestamp,
messageHash: details?.messageHash,
});
msgModel = DisappearingMessages.getMessageReadyToDisappear(
incomingCallConversation,
msgModel,
0,
details?.expireDetails
);
await msgModel.commit();
}
function getOwnerOfCallUUID(callUUID: string) {
@ -1344,7 +1360,8 @@ function getOwnerOfCallUUID(callUUID: string) {
export async function handleCallTypeAnswer(
sender: string,
callMessage: SignalService.CallMessage,
envelopeTimestamp: number
envelopeTimestamp: number,
expireDetails: (WithOptExpireUpdate & WithMessageHash) | null
) {
if (!callMessage.sdps || callMessage.sdps.length === 0) {
window.log.warn('cannot handle answered message without signal description proto sdps');
@ -1392,7 +1409,11 @@ export async function handleCallTypeAnswer(
}
window.log.info(`handling callMessage ANSWER from ${callMessageUUID}`);
const cachedMessage = getCachedMessageFromCallMessage(callMessage, envelopeTimestamp);
const cachedMessage = getCachedMessageFromCallMessage(
callMessage,
envelopeTimestamp,
expireDetails
);
pushCallMessageToCallCache(sender, callMessageUUID, cachedMessage);
@ -1437,7 +1458,7 @@ export async function handleCallTypeIceCandidates(
return;
}
window.log.info('handling callMessage ICE_CANDIDATES');
const cachedMessage = getCachedMessageFromCallMessage(callMessage, envelopeTimestamp);
const cachedMessage = getCachedMessageFromCallMessage(callMessage, envelopeTimestamp, null); // we don't care about the expiredetails of those messages
pushCallMessageToCallCache(sender, remoteCallUUID, cachedMessage);
if (currentCallUUID && callMessage.uuid === currentCallUUID) {
@ -1478,7 +1499,7 @@ export async function handleOtherCallTypes(
window.log.warn('handleOtherCallTypes has no valid uuid');
return;
}
const cachedMessage = getCachedMessageFromCallMessage(callMessage, envelopeTimestamp);
const cachedMessage = getCachedMessageFromCallMessage(callMessage, envelopeTimestamp, null); // we don't care about the expireDetails of those other messages
pushCallMessageToCallCache(sender, remoteCallUUID, cachedMessage);
}

Loading…
Cancel
Save