session-desktop/ts/session/sending/MessageSentHandler.ts

215 lines
7.1 KiB
TypeScript

import _ from 'lodash';
import { getMessageById } from '../../data/data';
import { SignalService } from '../../protobuf';
import { ConversationController } from '../conversations';
import { MessageController } from '../messages';
import { OpenGroupMessage } from '../messages/outgoing';
import { EncryptionType, RawMessage } from '../types';
import { UserUtils } from '../utils';
// tslint:disable-next-line no-unnecessary-class
export class MessageSentHandler {
public static async handlePublicMessageSentSuccess(
sentMessage: OpenGroupMessage,
result: { serverId: number; serverTimestamp: number }
) {
const { serverId, serverTimestamp } = result;
try {
const foundMessage = await MessageSentHandler.fetchHandleMessageSentData(
sentMessage
);
if (!foundMessage) {
throw new Error(
'handlePublicMessageSentSuccess(): The message should be in memory for an openGroup message'
);
}
foundMessage.set({
serverTimestamp,
serverId,
isPublic: true,
sent: true,
sent_at: sentMessage.timestamp,
sync: true,
synced: true,
sentSync: true,
});
await foundMessage.commit();
foundMessage.getConversation()?.updateLastMessage();
} catch (e) {
window.log.error('Error setting public on message');
}
}
public static async handleMessageSentSuccess(
sentMessage: RawMessage,
wrappedEnvelope?: Uint8Array
) {
// The wrappedEnvelope will be set only if the message is not one of OpenGroupMessage type.
const fetchedMessage = await MessageSentHandler.fetchHandleMessageSentData(
sentMessage
);
if (!fetchedMessage) {
return;
}
let sentTo = fetchedMessage.get('sent_to') || [];
let isOurDevice = false;
if (sentMessage.device) {
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 === EncryptionType.ClosedGroup;
// 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');
const contentDecoded = SignalService.Content.decode(
sentMessage.plainTextBuffer
);
const { dataMessage } = contentDecoded;
/**
* We should hit the notify endpoint for push notification only if:
* • It's a one-to-one chat or a closed group
* • The message has either text or attachments
*/
const hasBodyOrAttachments = Boolean(
dataMessage &&
(dataMessage.body ||
(dataMessage.attachments && dataMessage.attachments.length))
);
const shouldNotifyPushServer = hasBodyOrAttachments && !isOurDevice;
if (shouldNotifyPushServer) {
// notify the push notification server if needed
if (!wrappedEnvelope) {
window.log.warn('Should send PN notify but no wrapped envelope set.');
} else {
if (!window.LokiPushNotificationServer) {
window.LokiPushNotificationServer = new window.LokiPushNotificationServerApi();
}
window.LokiPushNotificationServer.notify(
wrappedEnvelope,
sentMessage.device
);
}
}
// Handle the sync logic here
if (shouldTriggerSyncMessage) {
if (dataMessage) {
try {
await fetchedMessage.sendSyncMessage(
dataMessage as SignalService.DataMessage,
sentMessage.timestamp
);
} catch (e) {
window.log.warn('Got an error while trying to sendSyncMessage():', e);
}
}
} else if (shouldMarkMessageAsSynced) {
fetchedMessage.set({ synced: true });
}
sentTo = _.union(sentTo, [sentMessage.device]);
fetchedMessage.set({
sent_to: sentTo,
sent: true,
expirationStartTimestamp: Date.now(),
sent_at: sentMessage.timestamp,
});
await fetchedMessage.commit();
fetchedMessage.getConversation()?.updateLastMessage();
}
public static async handleMessageSentFailure(
sentMessage: RawMessage | OpenGroupMessage,
error: any
) {
const fetchedMessage = await MessageSentHandler.fetchHandleMessageSentData(
sentMessage
);
if (!fetchedMessage) {
return;
}
if (error instanceof Error) {
await fetchedMessage.saveErrors(error);
}
if (!(sentMessage instanceof OpenGroupMessage)) {
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 });
}
fetchedMessage.set({
expirationStartTimestamp: Date.now(),
});
}
// 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();
}
/**
* 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, recieved 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.
*/
private static async fetchHandleMessageSentData(
m: RawMessage | OpenGroupMessage
) {
// if a message was sent and this message was sent after the last app restart,
// this message is still in memory in the MessageController
const msg = MessageController.getInstance().get(m.identifier);
if (!msg || !msg.message) {
// otherwise, look for it in the database
// nobody is listening to this freshly fetched message .trigger calls
// so we can just update the fields on the database
const dbMessage = await getMessageById(m.identifier);
if (!dbMessage) {
return null;
}
MessageController.getInstance().register(m.identifier, dbMessage);
return dbMessage;
}
return msg.message;
}
}