fix: repaired closed group disappearing messages

pull/2660/head
William Grant 2 years ago
parent 42356b0d60
commit 12087da2be

@ -654,8 +654,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({ const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({
chatMessage: chatMessageMediumGroup, chatMessage: chatMessageMediumGroup,
groupId: destination, groupId: destination,
expirationType, timestamp: sentAt,
expireTimer, expirationType: chatMessageParams.expirationType,
expireTimer: chatMessageParams.expireTimer,
}); });
// we need the return await so that errors are caught in the catch {} // we need the return await so that errors are caught in the catch {}
@ -760,6 +761,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({ const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({
chatMessage: chatMessageMediumGroup, chatMessage: chatMessageMediumGroup,
groupId: destination, groupId: destination,
timestamp: sentAt,
}); });
// we need the return await so that errors are caught in the catch {} // we need the return await so that errors are caught in the catch {}
await getMessageQueue().sendToGroup(closedGroupVisibleMessage); await getMessageQueue().sendToGroup(closedGroupVisibleMessage);

@ -941,10 +941,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
); );
} }
const timestamp = Date.now(); // force a new timestamp to handle user fixed his clock;
const chatParams = { const chatParams = {
identifier: this.id, identifier: this.id,
body, body,
timestamp: Date.now(), // force a new timestamp to handle user fixed his clock timestamp,
expireTimer: this.get('expireTimer'), expireTimer: this.get('expireTimer'),
attachments, attachments,
preview: preview ? [preview] : [], preview: preview ? [preview] : [],
@ -978,8 +980,9 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({ const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({
identifier: this.id, identifier: this.id,
chatMessage,
groupId: this.get('conversationId'), groupId: this.get('conversationId'),
timestamp,
chatMessage,
}); });
return getMessageQueue().sendToGroup(closedGroupVisibleMessage); return getMessageQueue().sendToGroup(closedGroupVisibleMessage);

@ -29,7 +29,10 @@ import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMe
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 { ConversationAttributes, ConversationTypeEnum } from '../../models/conversationAttributes'; import { ConversationAttributes, ConversationTypeEnum } from '../../models/conversationAttributes';
import { DisappearingMessageConversationType } from '../../util/expiringMessages'; import {
DisappearingMessageConversationType,
setExpirationStartTimestamp,
} from '../../util/expiringMessages';
export type GroupInfo = { export type GroupInfo = {
id: string; id: string;
@ -87,8 +90,8 @@ export async function initiateClosedGroupUpdate(
// remove from the zombies list the zombies not which are not in the group anymore // remove from the zombies list the zombies not which are not in the group anymore
zombies: convo.get('zombies')?.filter(z => members.includes(z)), zombies: convo.get('zombies')?.filter(z => members.includes(z)),
activeAt: Date.now(), activeAt: Date.now(),
expirationType: convo.get('expirationType'), // TODO Does this have a default value expirationType: convo.get('expirationType') || undefined,
expireTimer: convo.get('expireTimer'), expireTimer: convo.get('expireTimer') || 0,
}; };
const diff = buildGroupDiff(convo, groupDetails); const diff = buildGroupDiff(convo, groupDetails);
@ -100,8 +103,6 @@ export async function initiateClosedGroupUpdate(
name: groupName, name: groupName,
members, members,
admins: convo.get('groupAdmins'), admins: convo.get('groupAdmins'),
expirationType: convo.get('expirationType'),
expireTimer: convo.get('expireTimer'),
}; };
if (diff.newName?.length) { if (diff.newName?.length) {
@ -171,25 +172,36 @@ export async function addUpdateMessage(
groupUpdate.kicked = diff.kickedMembers; groupUpdate.kicked = diff.kickedMembers;
} }
if (UserUtils.isUsFromCache(sender)) { const expirationType = convo.get('expirationType');
const outgoingMessage = await convo.addSingleOutgoingMessage({ const expireTimer = convo.get('expireTimer');
const msgModel = {
sent_at: sentAt, sent_at: sentAt,
group_update: groupUpdate, group_update: groupUpdate,
expireTimer: 0, expirationType: expirationType || undefined,
}); expireTimer: expireTimer || 0,
expirationStartTimestamp:
expirationType === 'deleteAfterSend'
? setExpirationStartTimestamp(expirationType, sentAt)
: undefined,
};
if (UserUtils.isUsFromCache(sender)) {
const outgoingMessage = await convo.addSingleOutgoingMessage(msgModel);
return outgoingMessage; return outgoingMessage;
} }
const incomingMessage = await convo.addSingleIncomingMessage({ const incomingMessage = await convo.addSingleIncomingMessage({
sent_at: sentAt, ...msgModel,
group_update: groupUpdate,
expireTimer: 0,
source: sender, source: sender,
}); });
// update the unreadCount for this convo // update the unreadCount for this convo
const unreadCount = await convo.getUnreadCount(); const unreadCount = await convo.getUnreadCount();
convo.set({ convo.set({
unreadCount, unreadCount,
}); });
await convo.commit(); await convo.commit();
return incomingMessage; return incomingMessage;
} }
@ -238,6 +250,8 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
| 'left' | 'left'
| 'lastJoinedTimestamp' | 'lastJoinedTimestamp'
| 'zombies' | 'zombies'
| 'expirationType'
| 'expireTimer'
> = { > = {
displayNameInProfile: details.name, displayNameInProfile: details.name,
members: details.members, members: details.members,
@ -247,6 +261,8 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
active_at: details.activeAt ? details.activeAt : 0, active_at: details.activeAt ? details.activeAt : 0,
left: details.activeAt ? false : true, left: details.activeAt ? false : true,
lastJoinedTimestamp: details.activeAt && weWereJustAdded ? Date.now() : details.activeAt || 0, lastJoinedTimestamp: details.activeAt && weWereJustAdded ? Date.now() : details.activeAt || 0,
expirationType: details.expirationType || 'off',
expireTimer: details.expireTimer || 0,
}; };
conversation.set(updates); conversation.set(updates);
@ -264,12 +280,18 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
const { expirationType, expireTimer } = details; const { expirationType, expireTimer } = details;
if (expireTimer === undefined || typeof expireTimer !== 'number') { if (
expirationType === undefined ||
expirationType === 'off' ||
expireTimer === undefined ||
typeof expireTimer !== 'number'
) {
return; return;
} }
await conversation.updateExpireTimer({ await conversation.updateExpireTimer({
// TODO clean up 2 weeks after release // TODO clean up 2 weeks after release?
// TODO What are we cleaning?
providedExpirationType: expirationType || 'deleteAfterSend', providedExpirationType: expirationType || 'deleteAfterSend',
providedExpireTimer: expireTimer, providedExpireTimer: expireTimer,
providedChangeTimestamp: getNowWithNetworkOffset(), providedChangeTimestamp: getNowWithNetworkOffset(),

@ -1,32 +1,26 @@
import { SignalService } from '../../../../../protobuf'; import { SignalService } from '../../../../../protobuf';
import { DisappearingMessageType } from '../../../../../util/expiringMessages';
import { PubKey } from '../../../../types'; import { PubKey } from '../../../../types';
import { ContentMessage } from '../../ContentMessage'; import { ExpirableMessage, ExpirableMessageParams } from '../../ExpirableMessage';
import { MessageParams } from '../../Message';
export interface ClosedGroupMessageParams extends MessageParams { export interface ClosedGroupMessageParams extends ExpirableMessageParams {
groupId: string | PubKey; groupId: string | PubKey;
expirationType?: DisappearingMessageType;
expireTimer?: number;
} }
export abstract class ClosedGroupMessage extends ContentMessage { export abstract class ClosedGroupMessage extends ExpirableMessage {
public readonly groupId: PubKey; public readonly groupId: PubKey;
public readonly expirationType?: DisappearingMessageType;
public readonly expireTimer?: number;
constructor(params: ClosedGroupMessageParams) { constructor(params: ClosedGroupMessageParams) {
super({ super({
timestamp: params.timestamp, timestamp: params.timestamp,
identifier: params.identifier, identifier: params.identifier,
expirationType: params.expirationType,
expireTimer: params.expireTimer,
}); });
this.groupId = PubKey.cast(params.groupId); this.groupId = PubKey.cast(params.groupId);
if (!this.groupId || this.groupId.key.length === 0) { if (!this.groupId || this.groupId.key.length === 0) {
throw new Error('groupId must be set'); throw new Error('groupId must be set');
} }
this.expirationType = params.expirationType;
this.expireTimer = params.expireTimer;
} }
public static areAdminsMembers(admins: Array<string>, members: Array<string>) { public static areAdminsMembers(admins: Array<string>, members: Array<string>) {
@ -36,11 +30,12 @@ export abstract class ClosedGroupMessage extends ContentMessage {
public contentProto(): SignalService.Content { public contentProto(): SignalService.Content {
return new SignalService.Content({ return new SignalService.Content({
dataMessage: this.dataProto(), dataMessage: this.dataProto(),
...super.contentProto(),
// Closed Groups only support 'deleteAfterSend'
expirationType: expirationType:
this.expirationType === 'deleteAfterSend' this.expirationType === 'deleteAfterSend'
? SignalService.Content.ExpirationType.DELETE_AFTER_SEND ? SignalService.Content.ExpirationType.DELETE_AFTER_SEND
: undefined, : undefined,
expirationTimer: this.expireTimer,
}); });
} }

@ -2,14 +2,14 @@ import { SignalService } from '../../../../protobuf';
import { PubKey } from '../../../types'; import { PubKey } from '../../../types';
import { StringUtils } from '../../../utils'; import { StringUtils } from '../../../utils';
import { VisibleMessage } from './VisibleMessage'; import { VisibleMessage } from './VisibleMessage';
import { ClosedGroupMessage } from '../controlMessage/group/ClosedGroupMessage'; import {
import { DisappearingMessageType } from '../../../../util/expiringMessages'; ClosedGroupMessage,
ClosedGroupMessageParams,
interface ClosedGroupVisibleMessageParams { } from '../controlMessage/group/ClosedGroupMessage';
identifier?: string;
groupId: string | PubKey; interface ClosedGroupVisibleMessageParams extends ClosedGroupMessageParams {
expirationType?: DisappearingMessageType; // TODO Do we need strings?
expireTimer?: number; // groupId: string | PubKey;
chatMessage: VisibleMessage; chatMessage: VisibleMessage;
} }
@ -24,7 +24,9 @@ export class ClosedGroupVisibleMessage extends ClosedGroupMessage {
expirationType: params.expirationType, expirationType: params.expirationType,
expireTimer: params.expireTimer, expireTimer: params.expireTimer,
}); });
this.chatMessage = params.chatMessage; this.chatMessage = params.chatMessage;
if (!params.groupId) { if (!params.groupId) {
throw new Error('ClosedGroupVisibleMessage: groupId must be set'); throw new Error('ClosedGroupVisibleMessage: groupId must be set');
} }

@ -1,15 +1,9 @@
import { SignalService } from '../../../../protobuf'; import { SignalService } from '../../../../protobuf';
import { DisappearingMessageType } from '../../../../util/expiringMessages'; import { VisibleMessage, VisibleMessageParams } from './VisibleMessage';
import { MessageParams } from '../Message';
import { VisibleMessage } from './VisibleMessage';
interface GroupInvitationMessageParams extends MessageParams { interface GroupInvitationMessageParams extends VisibleMessageParams {
url: string; url: string;
name: string; name: string;
// if disappearing messages is set for the conversation, we need to set it.
// otherwise, it will disable the expire timer on the receiving side.
expirationType?: DisappearingMessageType;
expireTimer?: number;
} }
export class GroupInvitationMessage extends VisibleMessage { export class GroupInvitationMessage extends VisibleMessage {
@ -34,8 +28,8 @@ export class GroupInvitationMessage extends VisibleMessage {
}); });
return new SignalService.DataMessage({ return new SignalService.DataMessage({
...super.dataProto(),
openGroupInvitation, openGroupInvitation,
expireTimer: this.expireTimer,
}); });
} }
} }

@ -14,12 +14,14 @@ describe('ClosedGroupVisibleMessage', () => {
groupId = TestUtils.generateFakePubKey(); groupId = TestUtils.generateFakePubKey();
}); });
it('can create empty message with timestamp, groupId and chatMessage', () => { it('can create empty message with timestamp, groupId and chatMessage', () => {
const timestamp = Date.now();
const chatMessage = new VisibleMessage({ const chatMessage = new VisibleMessage({
timestamp: Date.now(), timestamp,
body: 'body', body: 'body',
}); });
const message = new ClosedGroupVisibleMessage({ const message = new ClosedGroupVisibleMessage({
groupId, groupId,
timestamp,
chatMessage, chatMessage,
}); });
const plainText = message.plainTextBuffer(); const plainText = message.plainTextBuffer();
@ -43,22 +45,26 @@ describe('ClosedGroupVisibleMessage', () => {
}); });
it('correct ttl', () => { it('correct ttl', () => {
const timestamp = Date.now();
const chatMessage = new VisibleMessage({ const chatMessage = new VisibleMessage({
timestamp: Date.now(), timestamp,
}); });
const message = new ClosedGroupVisibleMessage({ const message = new ClosedGroupVisibleMessage({
groupId, groupId,
timestamp,
chatMessage, chatMessage,
}); });
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TTL_MAX); expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TTL_MAX);
}); });
it('has an identifier', () => { it('has an identifier', () => {
const timestamp = Date.now();
const chatMessage = new VisibleMessage({ const chatMessage = new VisibleMessage({
timestamp: Date.now(), timestamp,
}); });
const message = new ClosedGroupVisibleMessage({ const message = new ClosedGroupVisibleMessage({
groupId, groupId,
timestamp,
chatMessage, chatMessage,
}); });
expect(message.identifier).to.not.equal(null, 'identifier cannot be null'); expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
@ -66,13 +72,15 @@ describe('ClosedGroupVisibleMessage', () => {
}); });
it('should use the identifier passed into it over the one set in chatMessage', () => { it('should use the identifier passed into it over the one set in chatMessage', () => {
const timestamp = Date.now();
const chatMessage = new VisibleMessage({ const chatMessage = new VisibleMessage({
timestamp: Date.now(), timestamp,
body: 'body', body: 'body',
identifier: 'chatMessage', identifier: 'chatMessage',
}); });
const message = new ClosedGroupVisibleMessage({ const message = new ClosedGroupVisibleMessage({
groupId, groupId,
timestamp,
chatMessage, chatMessage,
identifier: 'closedGroupMessage', identifier: 'closedGroupMessage',
}); });
@ -80,13 +88,15 @@ describe('ClosedGroupVisibleMessage', () => {
}); });
it('should use the identifier of the chatMessage if one is not specified on the closed group message', () => { it('should use the identifier of the chatMessage if one is not specified on the closed group message', () => {
const timestamp = Date.now();
const chatMessage = new VisibleMessage({ const chatMessage = new VisibleMessage({
timestamp: Date.now(), timestamp,
body: 'body', body: 'body',
identifier: 'chatMessage', identifier: 'chatMessage',
}); });
const message = new ClosedGroupVisibleMessage({ const message = new ClosedGroupVisibleMessage({
groupId, groupId,
timestamp,
chatMessage, chatMessage,
}); });
expect(message.identifier).to.be.equal('chatMessage'); expect(message.identifier).to.be.equal('chatMessage');

@ -96,7 +96,11 @@ describe('Message Utils', () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const groupId = TestUtils.generateFakePubKey(); const groupId = TestUtils.generateFakePubKey();
const chatMessage = TestUtils.generateVisibleMessage(); const chatMessage = TestUtils.generateVisibleMessage();
const message = new ClosedGroupVisibleMessage({ chatMessage, groupId }); const message = new ClosedGroupVisibleMessage({
groupId,
timestamp: Date.now(),
chatMessage,
});
const rawMessage = await MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
expect(rawMessage.encryption).to.equal(SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE); expect(rawMessage.encryption).to.equal(SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE);

@ -68,10 +68,14 @@ export function generateOpenGroupV2RoomInfos(): OpenGroupRequestCommonType {
return { roomId: 'main', serverUrl: 'http://open.getsession.org' }; return { roomId: 'main', serverUrl: 'http://open.getsession.org' };
} }
export function generateClosedGroupMessage(groupId?: string): ClosedGroupVisibleMessage { export function generateClosedGroupMessage(
groupId?: string,
timestamp?: number
): ClosedGroupVisibleMessage {
return new ClosedGroupVisibleMessage({ return new ClosedGroupVisibleMessage({
identifier: uuid(), identifier: uuid(),
groupId: groupId ?? generateFakePubKey().key, groupId: groupId ?? generateFakePubKey().key,
timestamp: timestamp || Date.now(),
chatMessage: generateVisibleMessage(), chatMessage: generateVisibleMessage(),
}); });
} }

Loading…
Cancel
Save