feat: added expirationType and expireTimer to content message and updated message pipeline

this includes sync messages and updating the protobuf, haven't tested closed groups
pull/2660/head
William Grant 2 years ago
parent 0700ac4813
commit ee6607b96a

@ -17,10 +17,12 @@ message Envelope {
} }
message TypingMessage { message TypingMessage {
enum Action { enum Action {
STARTED = 0; STARTED = 0;
STOPPED = 1; STOPPED = 1;
} }
// @required // @required
required uint64 timestamp = 1; required uint64 timestamp = 1;
// @required // @required
@ -42,7 +44,32 @@ message MessageRequestResponse {
optional DataMessage.LokiProfile profile = 3; optional DataMessage.LokiProfile profile = 3;
} }
// TODO Syncing disappearing messages
// message SyncedExpiries {
// message SyncedConversationExpiries {
// message SyncedExpiry {
// // @required
// required string serverHash = 1; // messageHash for desktop and serverHash for iOS
// // @required
// required uint64 expirationTimestamp = 2; // this is only used for deleteAfterRead
// }
// // @required
// required string syncTarget = 1; // the conversationID those expiries are related to
// repeated SyncedExpiry expiries = 2;
// }
// repeated SyncedConversationExpiries conversationExpiries = 1;
// }
message Content { message Content {
enum ExpirationType {
DELETE_AFTER_SEND = 1;
DELETE_AFTER_READ = 2;
}
optional DataMessage dataMessage = 1; optional DataMessage dataMessage = 1;
optional CallMessage callMessage = 3; optional CallMessage callMessage = 3;
optional ReceiptMessage receiptMessage = 5; optional ReceiptMessage receiptMessage = 5;
@ -51,6 +78,10 @@ message Content {
optional DataExtractionNotification dataExtractionNotification = 8; optional DataExtractionNotification dataExtractionNotification = 8;
optional Unsend unsendMessage = 9; optional Unsend unsendMessage = 9;
optional MessageRequestResponse messageRequestResponse = 10; optional MessageRequestResponse messageRequestResponse = 10;
optional ExpirationType expirationType = 11;
optional uint32 expirationTimer = 12;
optional uint64 lastDisappearingMessageChangeTimestamp = 13;
// optional SyncedExpiries syncedExpiries = 14;
} }
message KeyPair { message KeyPair {
@ -130,7 +161,7 @@ message DataMessage {
message ClosedGroupControlMessage { message ClosedGroupControlMessage {
enum Type { enum Type {
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins, expireTimer NEW = 1; // publicKey, name, encryptionKeyPair, members, admins, expirationTimer
ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers
NAME_CHANGE = 4; // name NAME_CHANGE = 4; // name
MEMBERS_ADDED = 5; // members MEMBERS_ADDED = 5; // members
@ -156,7 +187,8 @@ message DataMessage {
repeated bytes members = 5; repeated bytes members = 5;
repeated bytes admins = 6; repeated bytes admins = 6;
repeated KeyPairWrapper wrappers = 7; repeated KeyPairWrapper wrappers = 7;
optional uint32 expireTimer = 8; // TODO Make sure rename doesn't break anything
optional uint32 expirationTimer = 8;
} }
@ -164,6 +196,7 @@ message DataMessage {
repeated AttachmentPointer attachments = 2; repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3; optional GroupContext group = 3;
optional uint32 flags = 4; optional uint32 flags = 4;
// TODO this will be removed 2 weeks after the release
optional uint32 expireTimer = 5; optional uint32 expireTimer = 5;
optional bytes profileKey = 6; optional bytes profileKey = 6;
optional uint64 timestamp = 7; optional uint64 timestamp = 7;

@ -41,6 +41,7 @@ import { getSodiumRenderer } from '../session/crypto';
import { encryptProfile } from '../util/crypto/profileEncrypter'; import { encryptProfile } from '../util/crypto/profileEncrypter';
import { uploadFileToFsWithOnionV4 } from '../session/apis/file_server_api/FileServerApi'; import { uploadFileToFsWithOnionV4 } from '../session/apis/file_server_api/FileServerApi';
import { DisappearingMessageConversationType } from '../util/expiringMessages'; import { DisappearingMessageConversationType } from '../util/expiringMessages';
import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
export const getCompleteUrlForV2ConvoId = async (convoId: string) => { export const getCompleteUrlForV2ConvoId = async (convoId: string) => {
if (convoId.match(openGroupV2ConversationIdRegex)) { if (convoId.match(openGroupV2ConversationIdRegex)) {
@ -356,10 +357,19 @@ export async function setDisappearingMessagesByConvoId(
return; return;
} }
const providedChangeTimestamp = getNowWithNetworkOffset();
if (!expirationType || expirationType === 'off' || !seconds || seconds <= 0) { if (!expirationType || expirationType === 'off' || !seconds || seconds <= 0) {
await conversation.updateExpireTimer('off'); await conversation.updateExpireTimer({
providedExpirationType: 'off',
providedChangeTimestamp,
});
} else { } else {
await conversation.updateExpireTimer(expirationType, seconds); await conversation.updateExpireTimer({
providedExpirationType: expirationType,
providedExpireTimer: seconds,
providedChangeTimestamp,
});
} }
} }

@ -78,7 +78,6 @@ import {
ConversationAttributes, ConversationAttributes,
ConversationNotificationSetting, ConversationNotificationSetting,
ConversationTypeEnum, ConversationTypeEnum,
DisappearingMessageConversationType,
fillConvoAttributesWithDefaults, fillConvoAttributesWithDefaults,
} from './conversationAttributes'; } from './conversationAttributes';
import { SogsBlinding } from '../session/apis/open_group_api/sogsv3/sogsBlinding'; import { SogsBlinding } from '../session/apis/open_group_api/sogsv3/sogsBlinding';
@ -97,6 +96,7 @@ import {
import { sogsV3FetchPreviewAndSaveIt } from '../session/apis/open_group_api/sogsv3/sogsV3FetchFile'; import { sogsV3FetchPreviewAndSaveIt } from '../session/apis/open_group_api/sogsv3/sogsV3FetchFile';
import { Reaction } from '../types/Reaction'; import { Reaction } from '../types/Reaction';
import { Reactions } from '../util/reactions'; import { Reactions } from '../util/reactions';
import { DisappearingMessageConversationType } from '../util/expiringMessages';
export class ConversationModel extends Backbone.Model<ConversationAttributes> { export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public updateLastMessage: () => any; public updateLastMessage: () => any;
@ -1016,31 +1016,34 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
} }
public async updateExpireTimer( public async updateExpireTimer({
providedExpirationType: DisappearingMessageConversationType, providedExpirationType,
providedExpireTimer?: number, providedExpireTimer,
providedSource?: string, providedChangeTimestamp,
receivedAt?: number, // is set if it comes from outside providedSource,
options: { receivedAt, // is set if it comes from outside
fromSync?: boolean; fromSync,
} = {}, shouldCommit = true,
shouldCommit = true }: {
): Promise<void> { providedExpirationType: DisappearingMessageConversationType;
providedExpireTimer?: number;
providedChangeTimestamp?: number;
providedSource?: string;
receivedAt?: number; // is set if it comes from outside
fromSync?: boolean;
shouldCommit?: boolean;
}): Promise<void> {
let expirationType = providedExpirationType; let expirationType = providedExpirationType;
let expireTimer = providedExpireTimer; let expireTimer = providedExpireTimer;
let source = providedSource; let source = providedSource;
defaults(options, { fromSync: false }); defaults({ fromSync }, { fromSync: false });
if (!expirationType) { if (!expirationType || !expireTimer) {
expirationType = 'off'; expirationType = 'off';
expireTimer = 0; expireTimer = 0;
} }
if (!expireTimer) {
expireTimer = 0;
}
if (this.get('expireTimer') === expireTimer || (!expireTimer && !this.get('expireTimer'))) { if (this.get('expireTimer') === expireTimer || (!expireTimer && !this.get('expireTimer'))) {
return; return;
} }
@ -1057,21 +1060,29 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
source = source || UserUtils.getOurPubKeyStrFromCache(); source = source || UserUtils.getOurPubKeyStrFromCache();
// When we add a disappearing messages notification to the conversation, we want it // When we add a disappearing messages notification to the conversation, we want it
// to be above the message that initiated that change, hence the subtraction. // to be above the message that initiated that change, hence the subtraction.
// TODO Will we use this for lastDisappearingMessageChangeTimestamp
const timestamp = (receivedAt || Date.now()) - 1; const timestamp = (receivedAt || Date.now()) - 1;
this.set({ expirationType, expireTimer }); this.set({
expirationType,
expireTimer,
lastDisappearingMessageChangeTimestamp: providedChangeTimestamp || undefined,
});
const lastDisappearingMessageChangeTimestamp = providedChangeTimestamp || 0;
// TODO Update for the new types of Disappearing Messages
const commonAttributes = { const commonAttributes = {
flags: SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, flags: SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
expirationTimerUpdate: { expirationTimerUpdate: {
expirationType,
expireTimer, expireTimer,
lastDisappearingMessageChangeTimestamp,
source, source,
fromSync: options.fromSync, fromSync,
}, },
expireTimer: 0, // TODO do we need this?
// expirationType,
// expireTimer,
}; };
let message: MessageModel | undefined; let message: MessageModel | undefined;
@ -1082,6 +1093,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
sent_at: timestamp, sent_at: timestamp,
}); });
} else { } else {
// TODO do we still want to handle expiration in incoming messages?
message = await this.addSingleIncomingMessage({ message = await this.addSingleIncomingMessage({
...commonAttributes, ...commonAttributes,
// Even though this isn't reflected to the user, we want to place the last seen // Even though this isn't reflected to the user, we want to place the last seen
@ -1106,23 +1118,28 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return; return;
} }
// TODO Update for the new types of Disappearing Messages
const expireUpdate = { const expireUpdate = {
identifier: message.id, identifier: message.id,
timestamp, timestamp,
expireTimer: expireTimer ? expireTimer : (null as number | null), expirationType,
expireTimer,
lastDisappearingMessageChangeTimestamp,
}; };
if (this.isMe()) { if (this.isMe()) {
// TODO Check that the args are correct
const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate); const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate);
return message.sendSyncMessageOnly(expirationTimerMessage); return message.sendSyncMessageOnly(expirationTimerMessage);
} }
if (this.isPrivate()) { if (this.isPrivate()) {
// TODO Check that the args are correct
const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate); const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate);
const pubkey = new PubKey(this.get('id')); const pubkey = new PubKey(this.get('id'));
await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage); await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage);
} else { } else {
// TODO Check that the args are correct
// Cannot be an open group
window?.log?.warn('TODO: Expiration update for closed groups are to be updated'); window?.log?.warn('TODO: Expiration update for closed groups are to be updated');
const expireUpdateForGroup = { const expireUpdateForGroup = {
...expireUpdate, ...expireUpdate,

@ -1028,20 +1028,25 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
this.set({ this.set({
sent_to: [UserUtils.getOurPubKeyStrFromCache()], sent_to: [UserUtils.getOurPubKeyStrFromCache()],
sent: true, sent: true,
// NOTE if disappearing message is deleteAfterRead then we don't use this
expirationStartTimestamp: now, expirationStartTimestamp: now,
}); });
await this.commit(); await this.commit();
const data = dataMessage instanceof DataMessage ? dataMessage.dataProto() : dataMessage; await this.sendSyncMessage(dataMessage, now);
await this.sendSyncMessage(data, now);
} }
public async sendSyncMessage(dataMessage: SignalService.DataMessage, sentTimestamp: number) { public async sendSyncMessage(
data: DataMessage | SignalService.DataMessage,
sentTimestamp: number
) {
if (this.get('synced') || this.get('sentSync')) { if (this.get('synced') || this.get('sentSync')) {
return; return;
} }
const dataMessage = data instanceof DataMessage ? data.dataProto() : data;
// if this message needs to be synced // if this message needs to be synced
if ( if (
dataMessage.body?.length || dataMessage.body?.length ||
@ -1052,10 +1057,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
if (!conversation) { if (!conversation) {
throw new Error('Cannot trigger syncMessage with unknown convo.'); throw new Error('Cannot trigger syncMessage with unknown convo.');
} }
const syncMessage = buildSyncMessage(this.id, dataMessage, conversation.id, sentTimestamp); const syncMessage = buildSyncMessage(this.id, data, conversation.id, sentTimestamp);
await getMessageQueue().sendSyncMessage(syncMessage); await getMessageQueue().sendSyncMessage(syncMessage);
} }
this.set({ sentSync: true }); this.set({
sentSync: true,
});
await this.commit(); await this.commit();
} }

@ -35,6 +35,7 @@ export function createSwarmMessageSentFromUs(args: {
const messageData: MessageAttributesOptionals = { const messageData: MessageAttributesOptionals = {
...getSharedAttributesForSwarmMessage(args), ...getSharedAttributesForSwarmMessage(args),
...getSharedAttributesForOutgoingMessage(), ...getSharedAttributesForOutgoingMessage(),
// TODO need to update this for delete after read
expirationStartTimestamp: Math.min(args.sentAt, Date.now()), expirationStartTimestamp: Math.min(args.sentAt, Date.now()),
}; };

@ -21,11 +21,12 @@ export interface MessageAttributes {
reacts?: ReactionList; reacts?: ReactionList;
reactsIndex?: number; reactsIndex?: number;
body?: string; body?: string;
// NOTE this is used for the logic
expirationType?: DisappearingMessageType; expirationType?: DisappearingMessageType;
expireTimer: number; expireTimer: number;
expirationStartTimestamp: number; expirationStartTimestamp: number;
expires_at?: number; expires_at?: number;
// TODO are having both variables redundant? // NOTE this is used for conversation setting
expirationTimerUpdate?: { expirationTimerUpdate?: {
expirationType: DisappearingMessageType; expirationType: DisappearingMessageType;
expireTimer: number; expireTimer: number;

@ -32,6 +32,7 @@ import { MessageModel } from '../models/message';
import { updateConfirmModal } from '../state/ducks/modalDialog'; import { updateConfirmModal } from '../state/ducks/modalDialog';
import { perfEnd, perfStart } from '../session/utils/Performance'; import { perfEnd, perfStart } from '../session/utils/Performance';
import { ConversationTypeEnum } from '../models/conversationAttributes'; import { ConversationTypeEnum } from '../models/conversationAttributes';
import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
export const distributingClosedGroupEncryptionKeyPairs = new Map<string, ECKeyPair>(); export const distributingClosedGroupEncryptionKeyPairs = new Map<string, ECKeyPair>();
@ -240,7 +241,8 @@ export async function handleNewClosedGroup(
return; return;
} }
const groupConvo = getConversationController().get(groupId); const groupConvo = getConversationController().get(groupId);
const expireTimer = groupUpdate.expireTimer; // TODO Rename to expirationTimer
const expireTimer = groupUpdate.expirationTimer;
if (groupConvo) { if (groupConvo) {
// if we did not left this group, just add the keypair we got if not already there // if we did not left this group, just add the keypair we got if not already there
@ -256,12 +258,13 @@ export async function handleNewClosedGroup(
); );
// TODO This is only applicable for old closed groups - will be removed in future // TODO This is only applicable for old closed groups - will be removed in future
await groupConvo.updateExpireTimer( await groupConvo.updateExpireTimer({
expireTimer === 0 ? 'off' : 'deleteAfterSend', providedExpirationType: expireTimer === 0 ? 'off' : 'deleteAfterSend',
expireTimer, providedExpireTimer: expireTimer,
sender, providedChangeTimestamp: getNowWithNetworkOffset(),
Date.now() providedSource: sender,
); receivedAt: Date.now(),
});
if (isKeyPairAlreadyHere) { if (isKeyPairAlreadyHere) {
window.log.info('Dropping already saved keypair for group', groupId); window.log.info('Dropping already saved keypair for group', groupId);
@ -322,12 +325,13 @@ export async function handleNewClosedGroup(
// envelope.timestamp and Date.now(). And we need to listen to those (some might even remove us) // envelope.timestamp and Date.now(). And we need to listen to those (some might even remove us)
convo.set('lastJoinedTimestamp', envelopeTimestamp); convo.set('lastJoinedTimestamp', envelopeTimestamp);
// TODO This is only applicable for old closed groups - will be removed in future // TODO This is only applicable for old closed groups - will be removed in future
await convo.updateExpireTimer( await convo.updateExpireTimer({
expireTimer === 0 ? 'off' : 'deleteAfterSend', providedExpirationType: expireTimer === 0 ? 'off' : 'deleteAfterSend',
expireTimer, providedExpireTimer: expireTimer,
sender, providedChangeTimestamp: getNowWithNetworkOffset(),
envelopeTimestamp providedSource: sender,
); receivedAt: envelopeTimestamp,
});
convo.updateLastMessage(); convo.updateLastMessage();
await convo.commit(); await convo.commit();

@ -398,13 +398,25 @@ export async function innerHandleSwarmContentMessage(
if (content.dataMessage.profileKey && content.dataMessage.profileKey.length === 0) { if (content.dataMessage.profileKey && content.dataMessage.profileKey.length === 0) {
content.dataMessage.profileKey = null; content.dataMessage.profileKey = null;
} }
perfStart(`handleSwarmDataMessage-${envelope.id}`); perfStart(`handleSwarmDataMessage-${envelope.id}`);
let expireUpdate = null;
if (content.expirationType && content.expirationTimer) {
expireUpdate = {
expirationType: content.expirationType,
expirationTimer: content.expirationTimer,
};
}
await handleSwarmDataMessage( await handleSwarmDataMessage(
envelope, envelope,
sentAtTimestamp, sentAtTimestamp,
content.dataMessage as SignalService.DataMessage, content.dataMessage as SignalService.DataMessage,
messageHash, messageHash,
senderConversationModel senderConversationModel,
expireUpdate
); );
perfEnd(`handleSwarmDataMessage-${envelope.id}`, 'handleSwarmDataMessage'); perfEnd(`handleSwarmDataMessage-${envelope.id}`, 'handleSwarmDataMessage');
return; return;

@ -152,7 +152,8 @@ export async function handleSwarmDataMessage(
sentAtTimestamp: number, sentAtTimestamp: number,
rawDataMessage: SignalService.DataMessage, rawDataMessage: SignalService.DataMessage,
messageHash: string, messageHash: string,
senderConversationModel: ConversationModel senderConversationModel: ConversationModel,
expireUpdate: any
): Promise<void> { ): Promise<void> {
window.log.info('handleSwarmDataMessage'); window.log.info('handleSwarmDataMessage');
@ -246,7 +247,8 @@ export async function handleSwarmDataMessage(
sentAtTimestamp, sentAtTimestamp,
cleanDataMessage, cleanDataMessage,
convoToAddMessageTo, convoToAddMessageTo,
() => removeFromCache(envelope) () => removeFromCache(envelope),
expireUpdate
); );
} }
@ -293,7 +295,8 @@ async function handleSwarmMessage(
sentAt: number, sentAt: number,
rawDataMessage: SignalService.DataMessage, rawDataMessage: SignalService.DataMessage,
convoToAddMessageTo: ConversationModel, convoToAddMessageTo: ConversationModel,
confirm: () => void confirm: () => void,
expireUpdate?: any
): Promise<void> { ): Promise<void> {
if (!rawDataMessage || !msgModel) { if (!rawDataMessage || !msgModel) {
window?.log?.warn('Invalid data passed to handleSwarmMessage.'); window?.log?.warn('Invalid data passed to handleSwarmMessage.');
@ -310,6 +313,7 @@ async function handleSwarmMessage(
sender: msgModel.get('source'), sender: msgModel.get('source'),
you: isUsFromCache(msgModel.get('source')), you: isUsFromCache(msgModel.get('source')),
}); });
if ( if (
convoToAddMessageTo.isPrivate() && convoToAddMessageTo.isPrivate() &&
msgModel.get('unread') && msgModel.get('unread') &&
@ -322,6 +326,7 @@ async function handleSwarmMessage(
confirm(); confirm();
return; return;
} }
const isDuplicate = await isSwarmMessageDuplicate({ const isDuplicate = await isSwarmMessageDuplicate({
source: msgModel.get('source'), source: msgModel.get('source'),
sentAt, sentAt,
@ -339,7 +344,8 @@ async function handleSwarmMessage(
toRegularMessage(rawDataMessage), toRegularMessage(rawDataMessage),
confirm, confirm,
msgModel.get('source'), msgModel.get('source'),
messageHash messageHash,
expireUpdate
); );
}); });
} }

@ -1,7 +1,7 @@
import { queueAttachmentDownloads } from './attachments'; import { queueAttachmentDownloads } from './attachments';
import { Quote } from './types'; import { Quote } from './types';
import _ from 'lodash'; import _, { isEmpty } from 'lodash';
import { getConversationController } from '../session/conversations'; import { getConversationController } from '../session/conversations';
import { ConversationModel } from '../models/conversation'; import { ConversationModel } from '../models/conversation';
import { MessageModel, sliceQuoteText } from '../models/message'; import { MessageModel, sliceQuoteText } from '../models/message';
@ -14,11 +14,10 @@ import { MessageDirection } from '../models/messageType';
import { LinkPreviews } from '../util/linkPreviews'; import { LinkPreviews } from '../util/linkPreviews';
import { GoogleChrome } from '../util'; import { GoogleChrome } from '../util';
import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates'; import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
import { import { ConversationTypeEnum } from '../models/conversationAttributes';
ConversationTypeEnum,
DisappearingMessageConversationType,
} from '../models/conversationAttributes';
import { getUsBlindedInThatServer } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys'; import { getUsBlindedInThatServer } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { DisappearingMessageConversationType } from '../util/expiringMessages';
import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
function contentTypeSupported(type: string): boolean { function contentTypeSupported(type: string): boolean {
const Chrome = GoogleChrome; const Chrome = GoogleChrome;
@ -185,7 +184,7 @@ export type RegularMessageType = Pick<
| 'reaction' | 'reaction'
| 'profile' | 'profile'
| 'profileKey' | 'profileKey'
// TODO Add expirationType and other new props // TODO Will be removed 2 weeks after release
| 'expireTimer' | 'expireTimer'
> & { isRegularMessage: true }; > & { isRegularMessage: true };
@ -317,23 +316,26 @@ async function handleExpirationTimerUpdateNoCommit(
expirationType: DisappearingMessageConversationType, expirationType: DisappearingMessageConversationType,
expireTimer: number expireTimer: number
) { ) {
const providedChangeTimestamp = getNowWithNetworkOffset();
message.set({ message.set({
expirationTimerUpdate: { expirationTimerUpdate: {
source, source,
expirationType: expirationType !== 'off' ? expirationType : null,
expireTimer, expireTimer,
lastDisappearingMessageChangeTimestamp: providedChangeTimestamp,
}, },
unread: 0, // mark the message as read. unread: 0, // mark the message as read.
}); });
conversation.set({ expirationType, expireTimer });
await conversation.updateExpireTimer( await conversation.updateExpireTimer({
expirationType, providedExpirationType: expirationType,
expireTimer, providedExpireTimer: expireTimer,
source, providedChangeTimestamp,
message.get('received_at'), providedSource: source,
{}, receivedAt: message.get('received_at'),
false shouldCommit: false,
); });
} }
export async function handleMessageJob( export async function handleMessageJob(
@ -342,7 +344,8 @@ export async function handleMessageJob(
regularDataMessage: RegularMessageType, regularDataMessage: RegularMessageType,
confirm: () => void, confirm: () => void,
source: string, source: string,
messageHash: string messageHash: string,
expireUpdate?: any
) { ) {
window?.log?.info( window?.log?.info(
`Starting handleMessageJob for message ${messageModel.idForLogging()}, ${messageModel.get( `Starting handleMessageJob for message ${messageModel.idForLogging()}, ${messageModel.get(
@ -356,11 +359,13 @@ export async function handleMessageJob(
); );
try { try {
messageModel.set({ flags: regularDataMessage.flags }); messageModel.set({ flags: regularDataMessage.flags });
// TODO update to handle the new disappearing message props // TODO remove 2 weeks after release
if (messageModel.isExpirationTimerUpdate()) { if (messageModel.isExpirationTimerUpdate() || !isEmpty(expireUpdate)) {
// TODO Account for expirationType and lastDisappearingMessageChangeTimestamp const { expireTimer: oldExpireTimer } = regularDataMessage;
const { expireTimer } = regularDataMessage; const expirationType = expireUpdate.expirationType;
const expireTimer = expireUpdate.expireTimer || oldExpireTimer;
// TODO compare types and change timestamps
// const oldTypeValue = conversation.get('expirationType'); // const oldTypeValue = conversation.get('expirationType');
const oldTimerValue = conversation.get('expireTimer'); const oldTimerValue = conversation.get('expireTimer');
if (expireTimer === oldTimerValue) { if (expireTimer === oldTimerValue) {
@ -370,11 +375,12 @@ export async function handleMessageJob(
); );
return; return;
} }
await handleExpirationTimerUpdateNoCommit( await handleExpirationTimerUpdateNoCommit(
conversation, conversation,
messageModel, messageModel,
source, source,
'deleteAfterSend', expirationType,
expireTimer expireTimer
); );
} else { } else {

@ -28,11 +28,8 @@ import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group
import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage'; import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage';
import { getSwarmPollingInstance } from '../apis/snode_api'; import { getSwarmPollingInstance } from '../apis/snode_api';
import { getNowWithNetworkOffset } from '../apis/snode_api/SNodeAPI'; import { getNowWithNetworkOffset } from '../apis/snode_api/SNodeAPI';
import { import { ConversationAttributes, ConversationTypeEnum } from '../../models/conversationAttributes';
ConversationAttributes, import { DisappearingMessageConversationType } from '../../util/expiringMessages';
ConversationTypeEnum,
DisappearingMessageConversationType,
} from '../../models/conversationAttributes';
export type GroupInfo = { export type GroupInfo = {
id: string; id: string;
@ -270,16 +267,16 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
if (expireTimer === undefined || typeof expireTimer !== 'number') { if (expireTimer === undefined || typeof expireTimer !== 'number') {
return; return;
} }
// Todo Update here
await conversation.updateExpireTimer( await conversation.updateExpireTimer({
expirationType || 'deleteAfterSend', // TODO clean up 2 weeks after release
expireTimer, providedExpirationType: expirationType || 'deleteAfterSend',
UserUtils.getOurPubKeyStrFromCache(), providedExpireTimer: expireTimer,
Date.now(), providedChangeTimestamp: getNowWithNetworkOffset(),
{ providedSource: UserUtils.getOurPubKeyStrFromCache(),
fromSync: true, receivedAt: Date.now(),
} fromSync: true,
); });
} }
export async function leaveClosedGroup(groupId: string) { export async function leaveClosedGroup(groupId: string) {

@ -1,5 +1,6 @@
import { DataMessage } from '..'; import { DataMessage } from '..';
import { SignalService } from '../../../../protobuf'; import { SignalService } from '../../../../protobuf';
import { DisappearingMessageType } from '../../../../util/expiringMessages';
import { PubKey } from '../../../types'; import { PubKey } from '../../../types';
import { StringUtils } from '../../../utils'; import { StringUtils } from '../../../utils';
import { MessageParams } from '../Message'; import { MessageParams } from '../Message';
@ -7,23 +8,44 @@ import { MessageParams } from '../Message';
interface ExpirationTimerUpdateMessageParams extends MessageParams { interface ExpirationTimerUpdateMessageParams extends MessageParams {
groupId?: string | PubKey; groupId?: string | PubKey;
syncTarget?: string | PubKey; syncTarget?: string | PubKey;
expirationType: DisappearingMessageType | null;
expireTimer: number | null; expireTimer: number | null;
lastDisappearingMessageChangeTimestamp: number | null;
} }
// Note the old disappearing messages used a data message for the expiration time.
// The new ones use properties on the Content Message
// We will remove support for the old one 2 weeks after the release
export class ExpirationTimerUpdateMessage extends DataMessage { export class ExpirationTimerUpdateMessage extends DataMessage {
public readonly groupId?: PubKey; public readonly groupId?: PubKey;
public readonly syncTarget?: string; public readonly syncTarget?: string;
public readonly expirationType: DisappearingMessageType | null;
public readonly expireTimer: number | null; public readonly expireTimer: number | null;
public readonly lastDisappearingMessageChangeTimestamp: number | null;
constructor(params: ExpirationTimerUpdateMessageParams) { constructor(params: ExpirationTimerUpdateMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier }); super({ timestamp: params.timestamp, identifier: params.identifier });
this.expirationType = params.expirationType;
this.expireTimer = params.expireTimer; this.expireTimer = params.expireTimer;
this.lastDisappearingMessageChangeTimestamp = params.lastDisappearingMessageChangeTimestamp;
const { groupId, syncTarget } = params; const { groupId, syncTarget } = params;
this.groupId = groupId ? PubKey.cast(groupId) : undefined; this.groupId = groupId ? PubKey.cast(groupId) : undefined;
this.syncTarget = syncTarget ? PubKey.cast(syncTarget).key : undefined; this.syncTarget = syncTarget ? PubKey.cast(syncTarget).key : undefined;
} }
public contentProto(): SignalService.Content {
return new SignalService.Content({
dataMessage: this.dataProto(),
expirationType:
this.expirationType === 'deleteAfterSend'
? SignalService.Content.ExpirationType.DELETE_AFTER_SEND
: SignalService.Content.ExpirationType.DELETE_AFTER_READ,
expirationTimer: this.expireTimer,
lastDisappearingMessageChangeTimestamp: this.lastDisappearingMessageChangeTimestamp,
});
}
public dataProto(): SignalService.DataMessage { public dataProto(): SignalService.DataMessage {
const data = new SignalService.DataMessage(); const data = new SignalService.DataMessage();
@ -46,6 +68,7 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
data.syncTarget = this.syncTarget; data.syncTarget = this.syncTarget;
} }
// TODO remove 2 weeks after the release
if (this.expireTimer) { if (this.expireTimer) {
data.expireTimer = this.expireTimer; data.expireTimer = this.expireTimer;
} }

@ -63,7 +63,7 @@ export class ClosedGroupNewMessage extends ClosedGroupMessage {
dataMessage.closedGroupControlMessage.admins = this.admins.map(fromHexToArray); dataMessage.closedGroupControlMessage.admins = this.admins.map(fromHexToArray);
dataMessage.closedGroupControlMessage.members = this.members.map(fromHexToArray); dataMessage.closedGroupControlMessage.members = this.members.map(fromHexToArray);
dataMessage.closedGroupControlMessage.expireTimer = this.expireTimer; dataMessage.closedGroupControlMessage.expirationTimer = this.expireTimer;
try { try {
dataMessage.closedGroupControlMessage.encryptionKeyPair = new SignalService.KeyPair(); dataMessage.closedGroupControlMessage.encryptionKeyPair = new SignalService.KeyPair();
dataMessage.closedGroupControlMessage.encryptionKeyPair.privateKey = new Uint8Array( dataMessage.closedGroupControlMessage.encryptionKeyPair.privateKey = new Uint8Array(

@ -26,6 +26,8 @@ import { DURATION } from '../constants';
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage'; import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse'; import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse';
import { PubKey } from '../types'; import { PubKey } from '../types';
import { DataMessage } from '../messages/outgoing';
import { DisappearingMessageType } from '../../util/expiringMessages';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp'; const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -294,16 +296,18 @@ const buildSyncVisibleMessage = (
const buildSyncExpireTimerMessage = ( const buildSyncExpireTimerMessage = (
identifier: string, identifier: string,
dataMessage: SignalService.DataMessage, expirationType: DisappearingMessageType,
expireTimer: number,
lastDisappearingMessageChangeTimestamp: number | null,
timestamp: number, timestamp: number,
syncTarget: string syncTarget: string
) => { ) => {
const expireTimer = dataMessage.expireTimer;
return new ExpirationTimerUpdateMessage({ return new ExpirationTimerUpdateMessage({
identifier, identifier,
timestamp, timestamp,
expirationType: expirationType || null,
expireTimer, expireTimer,
lastDisappearingMessageChangeTimestamp: lastDisappearingMessageChangeTimestamp || null,
syncTarget, syncTarget,
}); });
}; };
@ -317,24 +321,48 @@ export type SyncMessageType =
export const buildSyncMessage = ( export const buildSyncMessage = (
identifier: string, identifier: string,
dataMessage: SignalService.DataMessage, data: DataMessage | SignalService.DataMessage,
syncTarget: string, syncTarget: string,
sentTimestamp: number sentTimestamp: number
): VisibleMessage | ExpirationTimerUpdateMessage => { ): VisibleMessage | ExpirationTimerUpdateMessage => {
if ( if (
(dataMessage as any).constructor.name !== 'DataMessage' && (data as any).constructor.name !== 'DataMessage' &&
!(dataMessage instanceof SignalService.DataMessage) !(data instanceof SignalService.DataMessage)
) { ) {
window?.log?.warn('buildSyncMessage with something else than a DataMessage'); window?.log?.warn('buildSyncMessage with something else than a DataMessage');
} }
// TODO Remove DataMessage expireTimer 2 weeks after the release
const dataMessage = data instanceof DataMessage ? data.dataProto() : data;
const contentMessage = data instanceof DataMessage ? data.contentProto() : null;
const expirationType =
contentMessage?.expirationType === SignalService.Content.ExpirationType.DELETE_AFTER_SEND
? 'deleteAfterSend'
: contentMessage?.expirationType === SignalService.Content.ExpirationType.DELETE_AFTER_READ
? 'deleteAfterRead'
: null;
const expireTimer = contentMessage?.expirationTimer || dataMessage.expireTimer;
const lastDisappearingMessageChangeTimestamp = contentMessage?.lastDisappearingMessageChangeTimestamp
? Number(contentMessage?.lastDisappearingMessageChangeTimestamp)
: null;
if (!sentTimestamp || !_.isNumber(sentTimestamp)) { if (!sentTimestamp || !_.isNumber(sentTimestamp)) {
throw new Error('Tried to build a sync message without a sentTimestamp'); throw new Error('Tried to build a sync message without a sentTimestamp');
} }
// don't include our profileKey on syncing message. This is to be done by a ConfigurationMessage now // don't include our profileKey on syncing message. This is to be done by a ConfigurationMessage now
const timestamp = _.toNumber(sentTimestamp); const timestamp = _.toNumber(sentTimestamp);
if (dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE) { if (
return buildSyncExpireTimerMessage(identifier, dataMessage, timestamp, syncTarget); contentMessage?.expirationType &&
dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
) {
return buildSyncExpireTimerMessage(
identifier,
expirationType,
expireTimer,
lastDisappearingMessageChangeTimestamp,
timestamp,
syncTarget
);
} }
return buildSyncVisibleMessage(identifier, dataMessage, timestamp, syncTarget); return buildSyncVisibleMessage(identifier, dataMessage, timestamp, syncTarget);
}; };

Loading…
Cancel
Save