You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/session/sending/MessageSentHandler.ts

217 lines
7.4 KiB
TypeScript

import { union } from 'lodash';
import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
import { DisappearingMessages } from '../disappearing_messages';
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { OutgoingRawMessage, PubKey } from '../types';
import { UserUtils } from '../utils';
async function handlePublicMessageSentSuccess(
sentMessageIdentifier: string,
result: { serverId: number; serverTimestamp: number }
) {
const { serverId, serverTimestamp } = result;
try {
const foundMessage = await fetchHandleMessageSentData(sentMessageIdentifier);
if (!foundMessage) {
throw new Error(
'handlePublicMessageSentSuccess(): The message should be in memory for an openGroup message'
);
}
// serverTimestamp can be a fractional number where first part is seconds and second part is nanosecs depending on the pysogs version.
foundMessage.set({
serverTimestamp,
serverId,
isPublic: true,
sent: true,
sent_at: serverTimestamp, // we quote by sent_at, so we MUST sent_at: serverTimestamp
sync: true,
synced: true,
sentSync: true,
});
await foundMessage.commit();
foundMessage.getConversation()?.updateLastMessage();
} catch (e) {
window?.log?.error('Error setting public on message');
}
}
async function handlePublicMessageSentFailure(sentMessage: OpenGroupVisibleMessage, error: any) {
const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
if (!fetchedMessage) {
return;
}
if (error instanceof Error) {
await fetchedMessage.saveErrors(error);
}
// always mark the message as sent.
// the fact that we have errors on the sent is based on the saveErrors()
fetchedMessage.set({
sent: true,
});
await fetchedMessage.commit();
await fetchedMessage.getConversation()?.updateLastMessage();
}
async function handleSwarmMessageSentSuccess(
sentMessage: Pick<OutgoingRawMessage, 'device' | 'encryption' | 'identifier'> & {
/**
* plainTextBuffer is only required when sending a message to a 1o1,
* as we need it to encrypt it again for our linked devices (synced messages)
*/
plainTextBuffer: Uint8Array | null;
},
effectiveTimestamp: number,
storedHash: string | null
) {
// The wrappedEnvelope will be set only if the message is not one of OpenGroupV2Message type.
let fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
if (!fetchedMessage) {
return;
}
let sentTo = fetchedMessage.get('sent_to') || [];
const isOurDevice = UserUtils.isUsFromCache(sentMessage.device);
// FIXME this is not correct and will cause issues with syncing
// At this point the only way to check for medium
// group is by comparing the encryption type
const isClosedGroupMessage =
sentMessage.encryption === SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE ||
PubKey.is03Pubkey(sentMessage.device);
// We trigger a sync message only when the message is not to one of our devices, AND
// the message is not for an open group (there is no sync for opengroups, each device pulls all messages), AND
// if we did not sync or trigger a sync message for this specific message already
const shouldTriggerSyncMessage =
!isOurDevice &&
!isClosedGroupMessage &&
!fetchedMessage.get('synced') &&
!fetchedMessage.get('sentSync');
// A message is synced if we triggered a sync message (sentSync)
// and the current message was sent to our device (so a sync message)
const shouldMarkMessageAsSynced =
isOurDevice && fetchedMessage.get('sentSync') && isClosedGroupMessage;
// Handle the sync logic here
if (shouldTriggerSyncMessage && sentMessage && sentMessage.plainTextBuffer) {
try {
const contentDecoded = SignalService.Content.decode(sentMessage.plainTextBuffer);
if (contentDecoded && contentDecoded.dataMessage) {
try {
await fetchedMessage.sendSyncMessage(contentDecoded, effectiveTimestamp);
const tempFetchMessage = await fetchHandleMessageSentData(sentMessage.identifier);
if (!tempFetchMessage) {
window?.log?.warn(
'Got an error while trying to sendSyncMessage(): fetchedMessage is null'
);
return;
}
fetchedMessage = tempFetchMessage;
} catch (e) {
window?.log?.warn('Got an error while trying to sendSyncMessage():', e);
}
}
} catch (e) {
window.log.info(
'failed to decode content (excpected except if message was for a 1o1 as we need it to send the sync message'
);
}
} else if (shouldMarkMessageAsSynced) {
fetchedMessage.set({ synced: true });
}
sentTo = union(sentTo, [sentMessage.device]);
if (storedHash) {
fetchedMessage.updateMessageHash(storedHash);
}
fetchedMessage.set({
sent_to: sentTo,
sent: true,
sent_at: effectiveTimestamp,
});
DisappearingMessages.checkForExpiringOutgoingMessage(fetchedMessage, 'handleMessageSentSuccess');
await fetchedMessage.commit();
fetchedMessage.getConversation()?.updateLastMessage();
}
async function handleSwarmMessageSentFailure(
sentMessage: Pick<OutgoingRawMessage, 'device' | 'identifier'>,
error: any
) {
const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
if (!fetchedMessage) {
return;
}
if (error instanceof Error) {
await fetchedMessage.saveErrors(error);
}
const isOurDevice = UserUtils.isUsFromCache(sentMessage.device);
// if this message was for ourself, and it was not already synced,
// it means that we failed to sync it.
// so just remove the flag saying that we are currently sending the sync message
if (isOurDevice && !fetchedMessage.get('sync')) {
fetchedMessage.set({ sentSync: false });
}
// always mark the message as sent.
// the fact that we have errors on the sent is based on the saveErrors()
fetchedMessage.set({
sent: true,
});
// Disappeared messages that fail to send should not disappear
if (fetchedMessage.getExpirationType() && fetchedMessage.getExpireTimerSeconds() > 0) {
fetchedMessage.set({
expirationStartTimestamp: undefined,
});
window.log.warn(
`[handleSwarmMessageSentFailure] Stopping a message from disppearing until we retry the send operation. messageId: ${fetchedMessage.get(
'id'
)}`
);
}
await fetchedMessage.commit();
await fetchedMessage.getConversation()?.updateLastMessage();
}
/**
* This function tries to find a message by messageId by first looking on the MessageController.
* The MessageController holds all messages being in memory.
* Those are the messages sent recently, received recently, or the one shown to the user.
*
* If the app restarted, it's very likely those messages won't be on the memory anymore.
* In this case, this function will look for it in the database and return it.
* If the message is found on the db, it will also register it to the MessageController so our subsequent calls are quicker.
*/
async function fetchHandleMessageSentData(messageIdentifier: string) {
const dbMessage = await Data.getMessageById(messageIdentifier);
if (!dbMessage) {
return null;
}
return dbMessage;
}
export const MessageSentHandler = {
handlePublicMessageSentSuccess,
handlePublicMessageSentFailure,
handleSwarmMessageSentFailure,
handleSwarmMessageSentSuccess,
};