diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index cb1f00a66..53145bd93 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -233,11 +233,12 @@
"disappearingMessagesModeLegacy": "Legacy",
"disappearingMessagesModeLegacySubtitle": "Original version of disappearing messages.",
"disappearingMessagesDisabled": "Disappearing messages disabled",
- "disabledDisappearingMessages": "$name$ has turned off disappearing messages.",
- "youDisabledDisappearingMessages": "You have turned off disappearing messages.",
- "youDisabledYourDisappearingMessages": "You turned off disappearing messages. Messages you send will no longer disappear.",
+ "groupDisabledDisappearingMessages": "$name$ has turned disappearing messages off.",
+ "updateDisappearingMessagesFallback": "$name$ updated disappearing message settings.",
+ "youDisabledDisappearingMessages": "You have turned disappearing messages off.",
+ "youDisabledYourDisappearingMessages": "You turned disappearing messages off. Messages you send will no longer disappear.",
"youSetYourDisappearingMessages": "You set your messages to disappear $time$ after they have been $type$.",
- "theyDisabledTheirDisappearingMessages": "$name$ has turned off disappearing messages. Messages they send will no longer disappear.",
+ "theyDisabledTheirDisappearingMessages": "$name$ has turned disappearing messages off. Messages they send will no longer disappear.",
"theySetTheirDisappearingMessages": "$name$ has set their messages to disappear $time$ after they have been $type$.",
"timerSetTo": "Disappearing message time set to $time$",
"set": "Set",
@@ -271,10 +272,15 @@
"groupNameChangeFallback": "Group name updated.",
"groupAvatarChange": "Group display picture updated.",
- "groupOneJoined": "$name$ joined the group.",
- "groupYouJoined": "You joined the group.",
- "groupTwoJoined": "$first$ and $second$ joined the group.",
- "groupOthersJoined": "$name$ and $count$ others joined the group.",
+ "groupOneJoined": "$name$ was invited to join the group.",
+ "groupYouJoined": "You were invited to join the group.",
+ "groupTwoJoined": "$first$ and $second$ were invited to join the group.",
+ "groupOthersJoined": "$name$ and $count$ others were invited to join the group.",
+
+ "groupOneJoinedWithHistory": "$name$ was invited to join the group. Chat history was shared.",
+ "groupYouJoinedWithHistory": "You were invited to join the group. Chat history was shared.",
+ "groupTwoJoinedWithHistory": "$first$ and $second$ were invited to join the group. Chat history was shared.",
+ "groupOthersJoinedWithHistory": "$name$ and $count$ others were invited to join the group. Chat history was shared.",
"groupOneRemoved": "$name$ was removed from the group.",
"groupYouRemoved": "You were removed from the group.",
diff --git a/protos/SignalService.proto b/protos/SignalService.proto
index 04237ac4f..06da60761 100644
--- a/protos/SignalService.proto
+++ b/protos/SignalService.proto
@@ -112,7 +112,9 @@ message GroupUpdateMemberChangeMessage {
required Type type = 1;
repeated string memberSessionIds = 2;
- required bytes adminSignature = 3;
+ optional bool historyShared = 3;
+ required bytes adminSignature = 4;
+
}
diff --git a/ts/components/conversation/TimerNotification.tsx b/ts/components/conversation/TimerNotification.tsx
index d556d5afc..975530b46 100644
--- a/ts/components/conversation/TimerNotification.tsx
+++ b/ts/components/conversation/TimerNotification.tsx
@@ -150,7 +150,9 @@ function useTextToRender(props: PropsForExpirationTimer) {
case 'fromOther':
return disabled
? window.i18n(
- ownSideOnly ? 'theyDisabledTheirDisappearingMessages' : 'disabledDisappearingMessages',
+ ownSideOnly
+ ? 'theyDisabledTheirDisappearingMessages'
+ : 'groupDisabledDisappearingMessages',
[contact, timespanText]
)
: mode
diff --git a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx
index 359d196d4..507ae6a75 100644
--- a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx
+++ b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx
@@ -57,7 +57,7 @@ function changeOfMembersV2({
type,
us,
}: {
- type: 'added' | 'promoted' | 'removed';
+ type: 'added' | 'addedWithHistory' | 'promoted' | 'removed';
changedWithNames: Array;
us: PubkeyType;
}): string {
@@ -75,7 +75,13 @@ function changeOfMembersV2({
: 'Others';
const action =
- type === 'added' ? 'Joined' : type === 'promoted' ? 'Promoted' : ('Removed' as const);
+ type === 'addedWithHistory'
+ ? 'JoinedWithHistory'
+ : type === 'added'
+ ? 'Joined'
+ : type === 'promoted'
+ ? 'Promoted'
+ : ('Removed' as const);
const key = `group${subject}${action}` as const;
return window.i18n(
@@ -85,7 +91,7 @@ function changeOfMembersV2({
}
// TODO those lookups might need to be memoized
-const ChangeItemJoined = (added: Array): string => {
+const ChangeItemJoined = (added: Array, withHistory: boolean): string => {
if (!added.length) {
throw new Error('Group update add is missing contacts');
}
@@ -95,7 +101,7 @@ const ChangeItemJoined = (added: Array): string => {
if (isGroupV2) {
return changeOfMembersV2({
changedWithNames: mapIdsWithNames(added, names),
- type: 'added',
+ type: withHistory ? 'addedWithHistory' : 'added',
us,
});
}
@@ -182,7 +188,7 @@ const ChangeItem = (change: PropsForGroupUpdateType): string => {
case 'name':
return ChangeItemName(change.newName);
case 'add':
- return ChangeItemJoined(change.added);
+ return ChangeItemJoined(change.added, change.withHistory);
case 'left':
return ChangeItemLeft(change.left);
case 'kicked':
diff --git a/ts/models/message.ts b/ts/models/message.ts
index 9c78a0314..65a2cb937 100644
--- a/ts/models/message.ts
+++ b/ts/models/message.ts
@@ -455,6 +455,15 @@ export class MessageModel extends Backbone.Model {
const change: PropsForGroupUpdateAdd = {
type: 'add',
added: groupUpdate.joined as Array,
+ withHistory: false,
+ };
+ return { change, ...sharedProps };
+ }
+ if (groupUpdate.joinedWithHistory?.length) {
+ const change: PropsForGroupUpdateAdd = {
+ type: 'add',
+ added: groupUpdate.joined as Array,
+ withHistory: true,
};
return { change, ...sharedProps };
}
@@ -522,7 +531,6 @@ export class MessageModel extends Backbone.Model {
return undefined;
}
-
const readBy = this.get('read_by') || [];
if (Storage.get(SettingsKey.settingsReadReceipt) && readBy.length > 0) {
return 'read';
diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts
index f851afc0e..7361468b5 100644
--- a/ts/models/messageType.ts
+++ b/ts/models/messageType.ts
@@ -156,6 +156,7 @@ export type PropsForMessageRequestResponse = MessageRequestResponseMsg & {
export type MessageGroupUpdate = {
left?: Array;
joined?: Array;
+ joinedWithHistory?: Array;
kicked?: Array;
promoted?: Array;
name?: string;
diff --git a/ts/receiver/closedGroups.ts b/ts/receiver/closedGroups.ts
index 3c417a299..0ae1bd6d6 100644
--- a/ts/receiver/closedGroups.ts
+++ b/ts/receiver/closedGroups.ts
@@ -689,6 +689,7 @@ async function handleClosedGroupMembersAdded(
const groupDiff: GroupDiff = {
type: 'add',
added: membersNotAlreadyPresent,
+ withHistory: false,
};
await ClosedGroup.addUpdateMessage({
convo,
diff --git a/ts/receiver/groupv2/handleGroupV2Message.ts b/ts/receiver/groupv2/handleGroupV2Message.ts
index b84767a73..7a6db1f06 100644
--- a/ts/receiver/groupv2/handleGroupV2Message.ts
+++ b/ts/receiver/groupv2/handleGroupV2Message.ts
@@ -285,7 +285,7 @@ async function handleGroupMemberChangeMessage({
switch (change.type) {
case SignalService.GroupUpdateMemberChangeMessage.Type.ADDED: {
await ClosedGroup.addUpdateMessage({
- diff: { type: 'add', added: filteredMemberChange },
+ diff: { type: 'add', added: filteredMemberChange, withHistory: change.historyShared },
...sharedDetails,
});
diff --git a/ts/session/group/closed-group.ts b/ts/session/group/closed-group.ts
index 0d01617c0..0f72a232f 100644
--- a/ts/session/group/closed-group.ts
+++ b/ts/session/group/closed-group.ts
@@ -89,11 +89,11 @@ async function initiateClosedGroupUpdate(
expireTimer,
};
- const diff = buildGroupDiff(convo, groupDetails);
+ const diff = buildGroupV1Diff(convo, groupDetails);
await updateOrCreateClosedGroup(groupDetails);
if (!diff) {
- window.log.warn('buildGroupDiff returned null');
+ window.log.warn('buildGroupV1Diff returned null');
await convo.commit();
return;
@@ -126,7 +126,7 @@ async function initiateClosedGroupUpdate(
}
if (diff.type === 'add' && diff.added?.length) {
- const joiningOnlyDiff: GroupDiff = _.pick(diff, ['type', 'added']);
+ const joiningOnlyDiff: GroupDiff = _.pick(diff, ['type', 'added', 'withHistory']);
const dbMessageAdded = await addUpdateMessage({
diff: joiningOnlyDiff,
@@ -213,7 +213,7 @@ export async function addUpdateMessage({
});
}
-function buildGroupDiff(convo: ConversationModel, update: GroupInfo): GroupDiff | null {
+function buildGroupV1Diff(convo: ConversationModel, update: GroupInfo): GroupDiff | null {
if (convo.getRealSessionUsername() !== update.name) {
return { type: 'name', newName: update.name };
}
@@ -228,7 +228,7 @@ function buildGroupDiff(convo: ConversationModel, update: GroupInfo): GroupDiff
PubKey.is05Pubkey
);
if (added.length > 0) {
- return { type: 'add', added };
+ return { type: 'add', added, withHistory: false };
}
// Check if anyone got kicked:
const removedMembers = _.difference(oldMembersWithZombies, newMembersWithZombiesLeft).filter(
diff --git a/ts/session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage.ts b/ts/session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage.ts
index 0def77978..dbb4f3370 100644
--- a/ts/session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage.ts
+++ b/ts/session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage.ts
@@ -12,17 +12,22 @@ import {
} from '../GroupUpdateMessage';
type MembersAddedMessageParams = GroupUpdateMessageParams & {
- typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.ADDED;
+ typeOfChange: 'added';
+ added: Array;
+};
+
+type MembersAddedWithHistoryMessageParams = GroupUpdateMessageParams & {
+ typeOfChange: 'addedWithHistory';
added: Array;
};
type MembersRemovedMessageParams = GroupUpdateMessageParams & {
- typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.REMOVED;
+ typeOfChange: 'removed';
removed: Array;
};
type MembersPromotedMessageParams = GroupUpdateMessageParams & {
- typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.PROMOTED;
+ typeOfChange: 'promoted';
promoted: Array;
};
@@ -30,7 +35,8 @@ type MembersPromotedMessageParams = GroupUpdateMessageParams & {
* GroupUpdateInfoChangeMessage is sent to the group's swarm.
*/
export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
- public readonly typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type;
+ public readonly typeOfChange: 'added' | 'addedWithHistory' | 'removed' | 'promoted';
+
public readonly memberSessionIds: Array = []; // added, removed, promoted based on the type.
public readonly namespace = SnodeNamespaces.ClosedGroupMessages;
private readonly secretKey: Uint8Array; // not sent, only used for signing content as part of the message
@@ -41,11 +47,11 @@ export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
| MembersAddedMessageParams
| MembersRemovedMessageParams
| MembersPromotedMessageParams
+ | MembersAddedWithHistoryMessageParams
) &
AdminSigDetails
) {
super(params);
- const { Type } = SignalService.GroupUpdateMemberChangeMessage;
const { typeOfChange } = params;
this.typeOfChange = typeOfChange;
@@ -53,21 +59,28 @@ export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
this.sodium = params.sodium;
switch (typeOfChange) {
- case Type.ADDED: {
+ case 'added': {
if (isEmpty(params.added)) {
throw new Error('added members list cannot be empty');
}
this.memberSessionIds = params.added;
break;
}
- case Type.REMOVED: {
+ case 'addedWithHistory': {
+ if (isEmpty(params.added)) {
+ throw new Error('addedWithHistory members list cannot be empty');
+ }
+ this.memberSessionIds = params.added;
+ break;
+ }
+ case 'removed': {
if (isEmpty(params.removed)) {
throw new Error('removed members list cannot be empty');
}
this.memberSessionIds = params.removed;
break;
}
- case Type.PROMOTED: {
+ case 'promoted': {
if (isEmpty(params.promoted)) {
throw new Error('promoted members list cannot be empty');
}
@@ -80,8 +93,15 @@ export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
}
public dataProto(): SignalService.DataMessage {
+ const { Type } = SignalService.GroupUpdateMemberChangeMessage;
+
const memberChangeMessage = new SignalService.GroupUpdateMemberChangeMessage({
- type: this.typeOfChange,
+ type:
+ this.typeOfChange === 'added' || this.typeOfChange === 'addedWithHistory'
+ ? Type.ADDED
+ : this.typeOfChange === 'removed'
+ ? Type.REMOVED
+ : Type.PROMOTED,
memberSessionIds: this.memberSessionIds,
adminSignature: this.sodium.crypto_sign_detached(
stringToUint8Array(`MEMBER_CHANGE${this.typeOfChange}${this.createAtNetworkTimestamp}`),
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 209041ada..fa40a296c 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -104,6 +104,7 @@ export type PropsForExpirationTimer = {
export type PropsForGroupUpdateAdd = {
type: 'add';
+ withHistory: boolean;
added: Array;
};
diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts
index 403f1fde7..7d5e0947c 100644
--- a/ts/state/ducks/metaGroups.ts
+++ b/ts/state/ducks/metaGroups.ts
@@ -608,8 +608,10 @@ async function getUpdateMessagesToPush({
removed,
adminSecretKey,
createAtNetworkTimestamp,
+ fromMemberLeftMessage,
}: WithAddWithHistoryMembers &
WithAddWithoutHistoryMembers &
+ WithFromMemberLeftMessage &
WithRemoveMembers &
WithFromCurrentDevice &
WithGroupPubkey & {
@@ -621,17 +623,29 @@ async function getUpdateMessagesToPush({
const updateMessages: Array = [];
- if (!fromCurrentDevice) {
+ if (!fromCurrentDevice || fromMemberLeftMessage) {
return updateMessages;
}
- const allAdded = [...withHistory, ...withoutHistory]; // those are already enforced to be unique (and without intersection) in `validateMemberChange()`
- if (allAdded.length) {
+ if (withoutHistory.length) {
+ updateMessages.push(
+ new GroupUpdateMemberChangeMessage({
+ added: withoutHistory,
+ groupPk,
+ typeOfChange: 'added',
+ createAtNetworkTimestamp,
+ secretKey: adminSecretKey,
+ sodium,
+ ...getConvoExpireDetailsForMsg(convo),
+ })
+ );
+ }
+ if (withHistory.length) {
updateMessages.push(
new GroupUpdateMemberChangeMessage({
- added: allAdded,
+ added: withHistory,
groupPk,
- typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.ADDED,
+ typeOfChange: 'addedWithHistory',
createAtNetworkTimestamp,
secretKey: adminSecretKey,
sodium,
@@ -644,7 +658,7 @@ async function getUpdateMessagesToPush({
new GroupUpdateMemberChangeMessage({
removed,
groupPk,
- typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.REMOVED,
+ typeOfChange: 'removed',
createAtNetworkTimestamp,
secretKey: adminSecretKey,
sodium,
@@ -652,6 +666,7 @@ async function getUpdateMessagesToPush({
})
);
}
+ // TODO might need to add the promote case here
return updateMessages;
}
@@ -704,6 +719,7 @@ async function handleMemberAddedFromUIOrNot({
withHistory,
withoutHistory,
createAtNetworkTimestamp,
+ fromMemberLeftMessage: false,
});
await LibSessionUtil.saveDumpsToDb(groupPk);
@@ -744,23 +760,21 @@ async function handleMemberAddedFromUIOrNot({
: null,
},
};
- const additions = updateMessages.find(
- m => m.typeOfChange === SignalService.GroupUpdateMemberChangeMessage.Type.ADDED
- );
- if (additions) {
+ const additionsWithoutHistory = updateMessages.find(m => m.typeOfChange === 'added');
+ const additionsWithHistory = updateMessages.find(m => m.typeOfChange === 'addedWithHistory');
+ if (additionsWithoutHistory) {
await ClosedGroup.addUpdateMessage({
- diff: { type: 'add', added: additions.memberSessionIds },
+ diff: { type: 'add', added: additionsWithoutHistory.memberSessionIds, withHistory: false },
...shared,
- expireUpdate: {
- expirationTimer: expiringDetails.expireTimer,
- expirationType: expiringDetails.expirationType,
- messageExpirationFromRetrieve:
- expiringDetails.expireTimer > 0
- ? createAtNetworkTimestamp + expiringDetails.expireTimer
- : null,
- },
});
}
+ if (additionsWithHistory) {
+ await ClosedGroup.addUpdateMessage({
+ diff: { type: 'add', added: additionsWithHistory.memberSessionIds, withHistory: true },
+ ...shared,
+ });
+ }
+
await convo.commit();
}
@@ -819,6 +833,7 @@ async function handleMemberRemovedFromUIOrNot({
withHistory: [],
withoutHistory: [],
createAtNetworkTimestamp,
+ fromMemberLeftMessage,
});
await LibSessionUtil.saveDumpsToDb(groupPk);
@@ -858,9 +873,7 @@ async function handleMemberRemovedFromUIOrNot({
},
};
- const removals = updateMessages.find(
- m => m.typeOfChange === SignalService.GroupUpdateMemberChangeMessage.Type.REMOVED
- );
+ const removals = updateMessages.find(m => m.typeOfChange === 'removed');
if (removals) {
await ClosedGroup.addUpdateMessage({
diff --git a/ts/state/selectors/selectedConversation.ts b/ts/state/selectors/selectedConversation.ts
index 18e8832fe..2b37eb784 100644
--- a/ts/state/selectors/selectedConversation.ts
+++ b/ts/state/selectors/selectedConversation.ts
@@ -87,12 +87,7 @@ export function getSelectedCanWrite(state: StateType) {
const isBlindedAndDisabledMsgRequests = getSelectedBlindedDisabledMsgRequests(state); // true if isPrivate, blinded and explicitely disabled msgreq
- return !(
- isBlocked ||
- isKickedFromGroup ||
- readOnlySogs ||
- isBlindedAndDisabledMsgRequests
- );
+ return !(isBlocked || isKickedFromGroup || readOnlySogs || isBlindedAndDisabledMsgRequests);
}
function getSelectedBlindedDisabledMsgRequests(state: StateType) {
diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts
index 6fc59115e..61e390416 100644
--- a/ts/types/LocalizerKeys.ts
+++ b/ts/types/LocalizerKeys.ts
@@ -142,7 +142,6 @@ export type LocalizerKeys =
| 'dialogClearAllDataDeletionFailedTitle'
| 'dialogClearAllDataDeletionFailedTitleQuestion'
| 'dialogClearAllDataDeletionQuestion'
- | 'disabledDisappearingMessages'
| 'disappearingMessages'
| 'disappearingMessagesDisabled'
| 'disappearingMessagesModeAfterRead'
@@ -206,6 +205,7 @@ export type LocalizerKeys =
| 'goToReleaseNotes'
| 'goToSupportPage'
| 'groupAvatarChange'
+ | 'groupDisabledDisappearingMessages'
| 'groupInviteFailedOne'
| 'groupInviteFailedOthers'
| 'groupInviteFailedTwo'
@@ -214,16 +214,20 @@ export type LocalizerKeys =
| 'groupNameChangeFallback'
| 'groupNamePlaceholder'
| 'groupOneJoined'
+ | 'groupOneJoinedWithHistory'
| 'groupOneLeft'
| 'groupOnePromoted'
| 'groupOneRemoved'
| 'groupOthersJoined'
+ | 'groupOthersJoinedWithHistory'
| 'groupOthersPromoted'
| 'groupOthersRemoved'
| 'groupTwoJoined'
+ | 'groupTwoJoinedWithHistory'
| 'groupTwoPromoted'
| 'groupTwoRemoved'
| 'groupYouJoined'
+ | 'groupYouJoinedWithHistory'
| 'groupYouLeft'
| 'groupYouPromoted'
| 'groupYouRemoved'
@@ -534,6 +538,7 @@ export type LocalizerKeys =
| 'unknownCountry'
| 'unpinConversation'
| 'unreadMessages'
+ | 'updateDisappearingMessagesFallback'
| 'updateGroupDialogTitle'
| 'userAddedToModerators'
| 'userBanFailed'
@@ -563,8 +568,8 @@ export type LocalizerKeys =
| 'youGotKickedFromGroup'
| 'youHaveANewFriendRequest'
| 'youLeftTheGroup'
- | 'youWereInvitedToGroup'
| 'youSetYourDisappearingMessages'
+ | 'youWereInvitedToGroup'
| 'yourSessionID'
| 'yourUniqueSessionID'
| 'zoomFactorSettingTitle';