diff --git a/ts/components/MemberListItem.tsx b/ts/components/MemberListItem.tsx
index bf164ced8..3252852a5 100644
--- a/ts/components/MemberListItem.tsx
+++ b/ts/components/MemberListItem.tsx
@@ -1,10 +1,12 @@
import styled from 'styled-components';
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
+import { useNicknameOrProfileNameOrShortenedPubkey } from '../hooks/useParamSelector';
+import { promoteUsersInGroup } from '../interactions/conversationInteractions';
import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
import { GroupInvite } from '../session/utils/job_runners/jobs/GroupInviteJob';
-import { GroupPromote } from '../session/utils/job_runners/jobs/GroupPromoteJob';
+import { hasClosedGroupV2QAButtons } from '../shared/env_vars';
import {
useMemberInviteFailed,
useMemberInviteSending,
@@ -13,6 +15,7 @@ import {
useMemberPromotionFailed,
useMemberPromotionSent,
} from '../state/selectors/groups';
+import { Avatar, AvatarSize, CrownIcon } from './avatar/Avatar';
import { Flex } from './basic/Flex';
import {
SessionButton,
@@ -20,10 +23,7 @@ import {
SessionButtonShape,
SessionButtonType,
} from './basic/SessionButton';
-import { useNicknameOrProfileNameOrShortenedPubkey } from '../hooks/useParamSelector';
-import { Avatar, AvatarSize, CrownIcon } from './avatar/Avatar';
import { SessionRadio } from './basic/SessionRadio';
-import { hasClosedGroupV2QAButtons } from '../shared/env_vars';
const AvatarContainer = styled.div`
position: relative;
@@ -252,7 +252,10 @@ const ResendPromoteButton = ({
buttonColor={SessionButtonColor.Danger}
text="PrOmOtE"
onClick={() => {
- void GroupPromote.addJob({ groupPk, member: pubkey });
+ void promoteUsersInGroup({
+ groupPk,
+ toPromote: [pubkey],
+ });
}}
/>
);
diff --git a/ts/components/dialog/InviteContactsDialog.tsx b/ts/components/dialog/InviteContactsDialog.tsx
index 5c25a99eb..5f7b95ecf 100644
--- a/ts/components/dialog/InviteContactsDialog.tsx
+++ b/ts/components/dialog/InviteContactsDialog.tsx
@@ -187,7 +187,7 @@ const InviteContactsDialogInner = (props: Props) => {
- {/* TODO: localize those strings once out releasing those buttons for real */}
+ {/* TODO: localize those strings once out releasing those buttons for real Remove after QA */}
{isGroupV2 && isDevProd() && (
<>
diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx
index 9c7722532..fd7e66cd1 100644
--- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx
+++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx
@@ -194,7 +194,7 @@ export const OverlayClosedGroupV2 = () => {
/>
- {/* TODO: localize those strings once out releasing those buttons for real */}
+ {/* TODO: localize those strings once out releasing those buttons for real Remove after QA */}
{isDevProd() && (
<>
diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx
index b33aa1c6d..d8bba60f4 100644
--- a/ts/components/menu/Menu.tsx
+++ b/ts/components/menu/Menu.tsx
@@ -32,7 +32,7 @@ import {
showBanUserByConvoId,
showInviteContactByConvoId,
showLeaveGroupByConvoId,
- showLeavePrivateConversationbyConvoId,
+ showLeavePrivateConversationByConvoId,
showRemoveModeratorsByConvoId,
showUnbanUserByConvoId,
showUpdateGroupNameByConvoId,
@@ -58,7 +58,10 @@ import { useSelectedConversationKey } from '../../state/selectors/selectedConver
import type { LocalizerToken } from '../../types/localizer';
import { SessionButtonColor } from '../basic/SessionButton';
import { ItemWithDataTestId } from './items/MenuItemWithDataTestId';
-import { ConversationInteractionStatus, ConversationInteractionType } from '../../interactions/types';
+import {
+ ConversationInteractionStatus,
+ ConversationInteractionType,
+} from '../../interactions/types';
/** Menu items standardized */
@@ -420,7 +423,7 @@ export const DeletePrivateConversationMenuItem = () => {
return (
{
- showLeavePrivateConversationbyConvoId(convoId);
+ showLeavePrivateConversationByConvoId(convoId);
}}
>
{isMe ? window.i18n('noteToSelfHide') : window.i18n('conversationsDelete')}
diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts
index 6107ee36a..5a5172660 100644
--- a/ts/interactions/conversationInteractions.ts
+++ b/ts/interactions/conversationInteractions.ts
@@ -1,4 +1,5 @@
-import { isNil } from 'lodash';
+import { isEmpty, isNil, uniq } from 'lodash';
+import { PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
import {
ConversationNotificationSettingType,
READ_MESSAGE_STATE,
@@ -53,6 +54,11 @@ import { BlockedNumberController } from '../util';
import { LocalizerComponentProps, LocalizerToken } from '../types/localizer';
import { sendInviteResponseToGroup } from '../session/sending/group/GroupInviteResponse';
import { NetworkTime } from '../util/NetworkTime';
+import { ClosedGroup } from '../session';
+import { GroupUpdateMessageFactory } from '../session/messages/message_factory/group/groupUpdateMessageFactory';
+import { GroupPromote } from '../session/utils/job_runners/jobs/GroupPromoteJob';
+import { MessageSender } from '../session/sending';
+import { StoreGroupRequestFactory } from '../session/apis/snode_api/factories/StoreGroupRequestFactory';
export async function copyPublicKeyByConvoId(convoId: string) {
if (OpenGroupUtils.isOpenGroupV2(convoId)) {
@@ -114,7 +120,7 @@ export const handleAcceptConversationRequest = async ({ convoId }: { convoId: st
if (PubKey.is03Pubkey(convoId)) {
const found = await UserGroupsWrapperActions.getGroup(convoId);
if (!found) {
- window.log.warn('cannot approve a non existing group in usergroup');
+ window.log.warn('cannot approve a non existing group in user group');
return null;
}
// this updates the wrapper and refresh the redux slice
@@ -307,7 +313,7 @@ export async function showUpdateGroupMembersByConvoId(conversationId: string) {
window.inboxStore?.dispatch(updateGroupMembersModal({ conversationId }));
}
-export function showLeavePrivateConversationbyConvoId(conversationId: string) {
+export function showLeavePrivateConversationByConvoId(conversationId: string) {
const conversation = ConvoHub.use().get(conversationId);
const isMe = conversation.isMe();
@@ -334,7 +340,7 @@ export function showLeavePrivateConversationbyConvoId(conversationId: string) {
});
await clearConversationInteractionState({ conversationId });
} catch (err) {
- window.log.warn(`showLeavePrivateConversationbyConvoId error: ${err}`);
+ window.log.warn(`showLeavePrivateConversationByConvoId error: ${err}`);
await saveConversationInteractionErrorAsMessage({
conversationId,
interactionType: isMe
@@ -954,3 +960,73 @@ async function saveConversationInteractionErrorAsMessage({
conversation.updateLastMessage();
}
+
+export async function promoteUsersInGroup({
+ groupPk,
+ toPromote,
+}: { toPromote: Array } & WithGroupPubkey) {
+ if (!toPromote.length) {
+ window.log.debug('promoteUsersInGroup: no users to promote');
+ return;
+ }
+
+ const convo = ConvoHub.use().get(groupPk);
+ if (!convo) {
+ window.log.debug('promoteUsersInGroup: group convo not found');
+ return;
+ }
+
+ const groupInWrapper = await UserGroupsWrapperActions.getGroup(groupPk);
+ if (!groupInWrapper || !groupInWrapper.secretKey || isEmpty(groupInWrapper.secretKey)) {
+ window.log.debug('promoteUsersInGroup: groupInWrapper not found or no secretkey');
+ return;
+ }
+
+ // push one group change message were initial members are added to the group
+ const membersHex = uniq(toPromote);
+ const sentAt = NetworkTime.now();
+ const us = UserUtils.getOurPubKeyStrFromCache();
+ const msgModel = await ClosedGroup.addUpdateMessage({
+ diff: { type: 'promoted', promoted: membersHex },
+ expireUpdate: null,
+ sender: us,
+ sentAt,
+ convo,
+ markAlreadySent: false, // the store below will mark the message as sent with dbMsgIdentifier
+ });
+ const groupMemberChange = await GroupUpdateMessageFactory.getPromotedControlMessage({
+ adminSecretKey: groupInWrapper.secretKey,
+ convo,
+ groupPk,
+ promoted: membersHex,
+ createAtNetworkTimestamp: sentAt,
+ dbMsgIdentifier: msgModel.id,
+ });
+
+ if (!groupMemberChange) {
+ window.log.warn('promoteUsersInGroup: failed to build group change');
+ throw new Error('promoteUsersInGroup: failed to build group change');
+ }
+
+ const storeRequests = await StoreGroupRequestFactory.makeGroupMessageSubRequest(
+ [groupMemberChange],
+ groupInWrapper
+ );
+
+ const result = await MessageSender.sendEncryptedDataToSnode({
+ destination: groupPk,
+ method: 'batch',
+ sortedSubRequests: storeRequests,
+ });
+
+ if (result?.[0].code !== 200) {
+ window.log.warn('promoteUsersInGroup: failed to store change');
+ throw new Error('promoteUsersInGroup: failed to store change');
+ }
+
+ for (let index = 0; index < membersHex.length; index++) {
+ const member = membersHex[index];
+ // eslint-disable-next-line no-await-in-loop
+ await GroupPromote.addJob({ groupPk, member });
+ }
+}
diff --git a/ts/models/message.ts b/ts/models/message.ts
index 7c11fe757..5791d1629 100644
--- a/ts/models/message.ts
+++ b/ts/models/message.ts
@@ -320,9 +320,9 @@ export class MessageModel extends Backbone.Model {
// TODO: clean up this typing
return window.i18n.stripped(...([token, args] as GetMessageArgs));
}
- if (groupUpdate.promoted) {
+ if (groupUpdate.promoted?.length) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
- const { token, args } = getPromotedGroupUpdateChangeStr(groupUpdate.kicked, groupName);
+ const { token, args } = getPromotedGroupUpdateChangeStr(groupUpdate.promoted, groupName);
// TODO: clean up this typing
return window.i18n.stripped(...([token, args] as GetMessageArgs));
}
@@ -1455,6 +1455,12 @@ export class MessageModel extends Backbone.Model {
? [groupUpdate.kicked]
: undefined;
+ forcedArrayUpdate.promoted = Array.isArray(groupUpdate.promoted)
+ ? groupUpdate.promoted
+ : groupUpdate.promoted
+ ? [groupUpdate.promoted]
+ : undefined;
+
forcedArrayUpdate.left = Array.isArray(groupUpdate.left)
? groupUpdate.left
: groupUpdate.left
diff --git a/ts/session/apis/snode_api/SnodeRequestTypes.ts b/ts/session/apis/snode_api/SnodeRequestTypes.ts
index b37014a86..1f849e2c0 100644
--- a/ts/session/apis/snode_api/SnodeRequestTypes.ts
+++ b/ts/session/apis/snode_api/SnodeRequestTypes.ts
@@ -17,15 +17,10 @@ import {
} from './namespaces';
import { GroupDetailsNeededForSignature, SnodeGroupSignature } from './signature/groupSignature';
import { SnodeSignature } from './signature/snodeSignatures';
-import {
- ShortenOrExtend,
- WithMessagesHashes,
- WithSecretKey,
- WithSignature,
- WithTimestamp,
-} from './types';
+import { ShortenOrExtend, WithMessagesHashes } from './types';
import { TTL_DEFAULT } from '../../constants';
import { NetworkTime } from '../../../util/NetworkTime';
+import { WithSecretKey, WithSignature, WithTimestamp } from '../../types/with';
type WithMaxSize = { max_size?: number };
export type WithShortenOrExtend = { shortenOrExtend: 'shorten' | 'extend' | '' };
diff --git a/ts/session/apis/snode_api/signature/snodeSignatures.ts b/ts/session/apis/snode_api/signature/snodeSignatures.ts
index 027aeadb8..fd49eb36f 100644
--- a/ts/session/apis/snode_api/signature/snodeSignatures.ts
+++ b/ts/session/apis/snode_api/signature/snodeSignatures.ts
@@ -11,14 +11,9 @@ import { PubKey } from '../../../types';
import { StringUtils, UserUtils } from '../../../utils';
import { fromHexToArray, fromUInt8ArrayToBase64 } from '../../../utils/String';
import { PreConditionFailed } from '../../../utils/errors';
-import {
- SignedHashesParams,
- WithMessagesHashes,
- WithShortenOrExtend,
- WithSignature,
- WithTimestamp,
-} from '../types';
+import { SignedHashesParams, WithMessagesHashes, WithShortenOrExtend } from '../types';
import { NetworkTime } from '../../../../util/NetworkTime';
+import { WithSignature, WithTimestamp } from '../../../types/with';
export type SnodeSignatureResult = WithSignature &
WithTimestamp & {
diff --git a/ts/session/apis/snode_api/types.ts b/ts/session/apis/snode_api/types.ts
index 127ce6d2c..e3eeae587 100644
--- a/ts/session/apis/snode_api/types.ts
+++ b/ts/session/apis/snode_api/types.ts
@@ -2,6 +2,7 @@ import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { SnodeNamespaces } from './namespaces';
import { SubaccountRevokeSubRequest, SubaccountUnrevokeSubRequest } from './SnodeRequestTypes';
+import { WithSignature, WithTimestamp } from '../../types/with';
export type RetrieveMessageItem = {
hash: string;
@@ -10,10 +11,9 @@ export type RetrieveMessageItem = {
storedAt: number; // **not** the envelope timestamp, but when the message was effectively stored on the snode
};
-
export type RetrieveMessageItemWithNamespace = RetrieveMessageItem & {
namespace: SnodeNamespaces; // the namespace from which this message was fetched
-}
+};
export type RetrieveMessagesResultsContent = {
hf?: Array;
@@ -31,9 +31,6 @@ export type WithMessagesHashes = { messagesHashes: Array };
export type RetrieveMessagesResultsBatched = Array;
-export type WithTimestamp = { timestamp: number };
-export type WithSignature = { signature: string };
-export type WithSecretKey = { secretKey: Uint8Array };
export type ShortenOrExtend = 'extend' | 'shorten' | '';
export type WithShortenOrExtend = { shortenOrExtend: ShortenOrExtend };
diff --git a/ts/session/messages/message_factory/group/groupUpdateMessageFactory.ts b/ts/session/messages/message_factory/group/groupUpdateMessageFactory.ts
new file mode 100644
index 000000000..97da4af52
--- /dev/null
+++ b/ts/session/messages/message_factory/group/groupUpdateMessageFactory.ts
@@ -0,0 +1,155 @@
+import { Uint8ArrayLen64, WithGroupPubkey } from 'libsession_util_nodejs';
+import { getSodiumRenderer } from '../../../crypto';
+import { DisappearingMessages } from '../../../disappearing_messages';
+
+import { GroupUpdateMemberChangeMessage } from '../../outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage';
+import { ConversationModel } from '../../../../models/conversation';
+import {
+ WithAddWithHistoryMembers,
+ WithAddWithoutHistoryMembers,
+ WithFromMemberLeftMessage,
+ WithPromotedMembers,
+ WithRemoveMembers,
+} from '../../../types/with';
+
+/**
+ * Return the control messages to be pushed to the group's swarm.
+ * Those are not going to change the state, they are just here as a "notification".
+ * i.e. "Alice was removed from the group"
+ */
+async function getRemovedControlMessage({
+ convo,
+ groupPk,
+ removed,
+ adminSecretKey,
+ createAtNetworkTimestamp,
+ fromMemberLeftMessage,
+ dbMsgIdentifier,
+}: WithFromMemberLeftMessage &
+ WithRemoveMembers &
+ WithGroupPubkey & {
+ convo: ConversationModel;
+ adminSecretKey: Uint8ArrayLen64;
+ createAtNetworkTimestamp: number;
+ dbMsgIdentifier: string;
+ }) {
+ const sodium = await getSodiumRenderer();
+
+ if (fromMemberLeftMessage || !removed.length) {
+ return null;
+ }
+
+ return new GroupUpdateMemberChangeMessage({
+ identifier: dbMsgIdentifier,
+ removed,
+ groupPk,
+ typeOfChange: 'removed',
+ createAtNetworkTimestamp,
+ secretKey: adminSecretKey,
+ sodium,
+ ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
+ });
+}
+
+async function getWithoutHistoryControlMessage({
+ convo,
+ withoutHistory,
+ groupPk,
+ adminSecretKey,
+ createAtNetworkTimestamp,
+ dbMsgIdentifier,
+}: WithAddWithoutHistoryMembers &
+ WithGroupPubkey & {
+ dbMsgIdentifier: string;
+ convo: ConversationModel;
+ adminSecretKey: Uint8ArrayLen64;
+ createAtNetworkTimestamp: number;
+ }) {
+ const sodium = await getSodiumRenderer();
+
+ if (!withoutHistory.length) {
+ return null;
+ }
+
+ return new GroupUpdateMemberChangeMessage({
+ identifier: dbMsgIdentifier,
+ added: withoutHistory,
+ groupPk,
+ typeOfChange: 'added',
+ createAtNetworkTimestamp,
+ secretKey: adminSecretKey,
+ sodium,
+ ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
+ });
+}
+
+async function getWithHistoryControlMessage({
+ convo,
+ withHistory,
+ groupPk,
+ adminSecretKey,
+ createAtNetworkTimestamp,
+ dbMsgIdentifier,
+}: WithAddWithHistoryMembers &
+ WithGroupPubkey & {
+ dbMsgIdentifier: string;
+ convo: ConversationModel;
+ adminSecretKey: Uint8ArrayLen64;
+ createAtNetworkTimestamp: number;
+ }) {
+ const sodium = await getSodiumRenderer();
+
+ if (!withHistory.length) {
+ return null;
+ }
+
+ return new GroupUpdateMemberChangeMessage({
+ identifier: dbMsgIdentifier,
+ added: withHistory,
+ groupPk,
+ typeOfChange: 'addedWithHistory',
+ createAtNetworkTimestamp,
+ secretKey: adminSecretKey,
+ sodium,
+ ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
+ });
+}
+
+async function getPromotedControlMessage({
+ convo,
+ promoted,
+ groupPk,
+ adminSecretKey,
+ createAtNetworkTimestamp,
+ dbMsgIdentifier,
+}: WithPromotedMembers &
+ WithGroupPubkey & {
+ dbMsgIdentifier: string;
+ convo: ConversationModel;
+ adminSecretKey: Uint8ArrayLen64;
+ createAtNetworkTimestamp: number;
+ }) {
+ const sodium = await getSodiumRenderer();
+
+ if (!promoted.length) {
+ return null;
+ }
+
+ return new GroupUpdateMemberChangeMessage({
+ identifier: dbMsgIdentifier,
+ promoted,
+ groupPk,
+ typeOfChange: 'promoted',
+ createAtNetworkTimestamp,
+ secretKey: adminSecretKey,
+ sodium,
+ ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
+ });
+}
+
+export const GroupUpdateMessageFactory = {
+ getRemovedControlMessage,
+ getWithoutHistoryControlMessage,
+ getWithHistoryControlMessage,
+ getPromotedControlMessage,
+};
diff --git a/ts/session/types/with.ts b/ts/session/types/with.ts
index 50f16911a..bd5cebcbb 100644
--- a/ts/session/types/with.ts
+++ b/ts/session/types/with.ts
@@ -1 +1,13 @@
+import { PubkeyType } from 'libsession_util_nodejs';
+
export type WithMessageHash = { messageHash: string };
+export type WithTimestamp = { timestamp: number };
+export type WithSignature = { signature: string };
+export type WithSecretKey = { secretKey: Uint8Array };
+
+export type WithFromMemberLeftMessage = { fromMemberLeftMessage: boolean }; // there are some changes we want to skip when doing changes triggered from a memberLeft message.
+
+export type WithAddWithoutHistoryMembers = { withoutHistory: Array };
+export type WithAddWithHistoryMembers = { withHistory: Array };
+export type WithRemoveMembers = { removed: Array };
+export type WithPromotedMembers = { promoted: Array };
diff --git a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts
index 81a0f332c..f250e2397 100644
--- a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts
+++ b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts
@@ -1,5 +1,5 @@
/* eslint-disable no-await-in-loop */
-import { PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
+import { WithGroupPubkey } from 'libsession_util_nodejs';
import { compact, isEmpty, isNumber } from 'lodash';
import { v4 } from 'uuid';
import { StringUtils } from '../..';
@@ -17,7 +17,6 @@ import {
} from '../../../apis/snode_api/SnodeRequestTypes';
import { StoreGroupRequestFactory } from '../../../apis/snode_api/factories/StoreGroupRequestFactory';
import { RevokeChanges, SnodeAPIRevoke } from '../../../apis/snode_api/revokeSubaccount';
-import { WithSecretKey } from '../../../apis/snode_api/types';
import { concatUInt8Array, getSodiumRenderer } from '../../../crypto';
import { GroupUpdateDeleteMemberContentMessage } from '../../../messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateDeleteMemberContentMessage';
import { MessageSender } from '../../../sending';
@@ -31,10 +30,12 @@ import {
} from '../PersistedJob';
import { GroupSync } from './GroupSyncJob';
import { NetworkTime } from '../../../../util/NetworkTime';
-
-export type WithAddWithoutHistoryMembers = { withoutHistory: Array };
-export type WithAddWithHistoryMembers = { withHistory: Array };
-export type WithRemoveMembers = { removed: Array };
+import {
+ WithAddWithHistoryMembers,
+ WithAddWithoutHistoryMembers,
+ WithRemoveMembers,
+ WithSecretKey,
+} from '../../../types/with';
const defaultMsBetweenRetries = 10000;
const defaultMaxAttempts = 1;
diff --git a/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts b/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts
index f4b60acbb..d36f6306e 100644
--- a/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts
+++ b/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts
@@ -20,7 +20,7 @@ import {
import { MessageQueue } from '../../../sending';
const defaultMsBetweenRetries = 10000;
-const defaultMaxAttemps = 1;
+const defaultMaxAttempts = 1;
type JobExtraArgs = {
groupPk: GroupPubkeyType;
@@ -75,7 +75,7 @@ class GroupPromoteJob extends PersistedJob {
member,
groupPk,
delayBetweenRetries: defaultMsBetweenRetries,
- maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttemps,
+ maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts,
nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries,
currentRetry: isNumber(currentRetry) ? currentRetry : 0,
});
diff --git a/ts/session/utils/job_runners/jobs/GroupSyncJob.ts b/ts/session/utils/job_runners/jobs/GroupSyncJob.ts
index 571974146..74d7dbd81 100644
--- a/ts/session/utils/job_runners/jobs/GroupSyncJob.ts
+++ b/ts/session/utils/job_runners/jobs/GroupSyncJob.ts
@@ -120,7 +120,7 @@ async function pushChangesToGroupSwarmIfNeeded({
if (window.sessionFeatureFlags.debug.debugLibsessionDumps) {
const dumps = await MetaGroupWrapperActions.metaMakeDump(groupPk);
window.log.info(
- `pushChangesToGroupSwarmIfNeeded: current metadump: ${ed25519Str(groupPk)}:`,
+ `pushChangesToGroupSwarmIfNeeded: current meta dump: ${ed25519Str(groupPk)}:`,
to_hex(dumps)
);
}
@@ -166,10 +166,10 @@ async function pushChangesToGroupSwarmIfNeeded({
});
const expectedReplyLength =
- (supplementalKeysSubRequest ? 1 : 0) + // we are sending all the supplemental keys as a single subrequest
- pendingConfigRequests.length + // each of those are sent as a subrequest
- extraStoreRequests.length + // each of those are sent as a subrequest
- extraRequestWithExpectedResults.length; // each of those are sent as a subrequest, but they don't all return something...
+ (supplementalKeysSubRequest ? 1 : 0) + // we are sending all the supplemental keys as a single sub request
+ pendingConfigRequests.length + // each of those are sent as a sub request
+ extraStoreRequests.length + // each of those are sent as a sub request
+ extraRequestWithExpectedResults.length; // each of those are sent as a sub request, but they don't all return something...
// we do a sequence call here. If we do not have the right expected number of results, consider it a failure
if (!isArray(result) || result.length !== expectedReplyLength) {
@@ -184,6 +184,7 @@ async function pushChangesToGroupSwarmIfNeeded({
const changes = LibSessionUtil.batchResultsToGroupSuccessfulChange(result, {
allOldHashes,
messages: pendingConfigData,
+
});
if (isEmpty(changes)) {
diff --git a/ts/session/utils/libsession/libsession_utils.ts b/ts/session/utils/libsession/libsession_utils.ts
index eda71af77..9af909b4f 100644
--- a/ts/session/utils/libsession/libsession_utils.ts
+++ b/ts/session/utils/libsession/libsession_utils.ts
@@ -284,7 +284,6 @@ function batchResultsToGroupSuccessfulChange(
* As it is a sequence, the delete might have failed but the new config message might still be posted.
* So we need to check which request failed, and if it is the delete by hashes, we need to add the hash of the posted message to the list of hashes
*/
-
if (!result?.length) {
return successfulChanges;
}
diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts
index 8ad2bc9cf..65ee71451 100644
--- a/ts/state/ducks/metaGroups.ts
+++ b/ts/state/ducks/metaGroups.ts
@@ -5,14 +5,13 @@ import {
GroupMemberGet,
GroupPubkeyType,
PubkeyType,
- Uint8ArrayLen64,
UserGroupsGet,
WithGroupPubkey,
WithPubkey,
} from 'libsession_util_nodejs';
import { intersection, isEmpty, uniq } from 'lodash';
+import { from_hex } from 'libsodium-wrappers-sumo';
import { ConfigDumpData } from '../../data/configDump/configDump';
-import { ConversationModel } from '../../models/conversation';
import { HexString } from '../../node/hexStrings';
import { SignalService } from '../../protobuf';
import { getSwarmPollingInstance } from '../../session/apis/snode_api';
@@ -27,12 +26,7 @@ import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { PreConditionFailed } from '../../session/utils/errors';
import { GroupInvite } from '../../session/utils/job_runners/jobs/GroupInviteJob';
-import {
- GroupPendingRemovals,
- WithAddWithHistoryMembers,
- WithAddWithoutHistoryMembers,
- WithRemoveMembers,
-} from '../../session/utils/job_runners/jobs/GroupPendingRemovalsJob';
+import { GroupPendingRemovals } from '../../session/utils/job_runners/jobs/GroupPendingRemovalsJob';
import { GroupSync } from '../../session/utils/job_runners/jobs/GroupSyncJob';
import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob';
import { RunJobResult } from '../../session/utils/job_runners/PersistedJob';
@@ -53,9 +47,14 @@ import { openConversationWithMessages } from './conversations';
import { resetLeftOverlayMode } from './section';
import { ConversationTypeEnum } from '../../models/types';
import { NetworkTime } from '../../util/NetworkTime';
-import { from_hex } from 'libsodium-wrappers-sumo';
+import { GroupUpdateMessageFactory } from '../../session/messages/message_factory/group/groupUpdateMessageFactory';
+import {
+ WithAddWithHistoryMembers,
+ WithAddWithoutHistoryMembers,
+ WithFromMemberLeftMessage,
+ WithRemoveMembers,
+} from '../../session/types/with';
-type WithFromMemberLeftMessage = { fromMemberLeftMessage: boolean }; // there are some changes we want to skip when doing changes triggered from a memberLeft message.
export type GroupState = {
infos: Record;
members: Record>;
@@ -206,7 +205,7 @@ const initNewGroupInWrapper = createAsyncThunk(
convo,
markAlreadySent: false, // the store below will mark the message as sent with dbMsgIdentifier
});
- groupMemberChange = await getWithoutHistoryControlMessage({
+ groupMemberChange = await GroupUpdateMessageFactory.getWithoutHistoryControlMessage({
adminSecretKey: groupSecretKey,
convo,
groupPk,
@@ -343,7 +342,7 @@ const handleUserGroupUpdate = createAsyncThunk(
/**
* Called only when the app just loaded the SessionInbox (i.e. user logged in and fully loaded).
- * This function populates the slice with any meta-dumps we have in the DB, if they also are part of what is the usergroup wrapper tracking.
+ * This function populates the slice with any meta-dumps we have in the DB, if they also are part of what is the user group wrapper tracking.
*
*/
const loadMetaDumpsFromDB = createAsyncThunk(
@@ -562,109 +561,6 @@ async function handleWithoutHistoryMembers({
}
}
-/**
- * Return the control messages to be pushed to the group's swarm.
- * Those are not going to change the state, they are just here as a "notification".
- * i.e. "Alice was removed from the group"
- */
-async function getRemovedControlMessage({
- convo,
- groupPk,
- removed,
- adminSecretKey,
- createAtNetworkTimestamp,
- fromMemberLeftMessage,
- dbMsgIdentifier,
-}: WithFromMemberLeftMessage &
- WithRemoveMembers &
- WithGroupPubkey & {
- convo: ConversationModel;
- adminSecretKey: Uint8ArrayLen64;
- createAtNetworkTimestamp: number;
- dbMsgIdentifier: string;
- }) {
- const sodium = await getSodiumRenderer();
-
- if (fromMemberLeftMessage || !removed.length) {
- return null;
- }
-
- return new GroupUpdateMemberChangeMessage({
- identifier: dbMsgIdentifier,
- removed,
- groupPk,
- typeOfChange: 'removed',
- createAtNetworkTimestamp,
- secretKey: adminSecretKey,
- sodium,
- ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
- });
-}
-
-async function getWithoutHistoryControlMessage({
- convo,
- withoutHistory,
- groupPk,
- adminSecretKey,
- createAtNetworkTimestamp,
- dbMsgIdentifier,
-}: WithAddWithoutHistoryMembers &
- WithGroupPubkey & {
- dbMsgIdentifier: string;
- convo: ConversationModel;
- adminSecretKey: Uint8ArrayLen64;
- createAtNetworkTimestamp: number;
- }) {
- const sodium = await getSodiumRenderer();
-
- if (!withoutHistory.length) {
- return null;
- }
-
- return new GroupUpdateMemberChangeMessage({
- identifier: dbMsgIdentifier,
- added: withoutHistory,
- groupPk,
- typeOfChange: 'added',
- createAtNetworkTimestamp,
- secretKey: adminSecretKey,
- sodium,
- ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
- });
-}
-
-async function getWithHistoryControlMessage({
- convo,
- withHistory,
- groupPk,
- adminSecretKey,
- createAtNetworkTimestamp,
- dbMsgIdentifier,
-}: WithAddWithHistoryMembers &
- WithGroupPubkey & {
- dbMsgIdentifier: string;
- convo: ConversationModel;
- adminSecretKey: Uint8ArrayLen64;
- createAtNetworkTimestamp: number;
- }) {
- const sodium = await getSodiumRenderer();
-
- if (!withHistory.length) {
- return null;
- }
-
- return new GroupUpdateMemberChangeMessage({
- identifier: dbMsgIdentifier,
- added: withHistory,
- groupPk,
- typeOfChange: 'addedWithHistory',
- createAtNetworkTimestamp,
- secretKey: adminSecretKey,
- sodium,
- ...DisappearingMessages.getExpireDetailsForOutgoingMessage(convo, createAtNetworkTimestamp),
- });
-}
-
async function handleMemberAddedFromUI({
addMembersWithHistory,
addMembersWithoutHistory,
@@ -728,7 +624,7 @@ async function handleMemberAddedFromUI({
diff: { type: 'add', added: withHistory, withHistory: true },
...shared,
});
- const groupChange = await getWithHistoryControlMessage({
+ const groupChange = await GroupUpdateMessageFactory.getWithHistoryControlMessage({
adminSecretKey: group.secretKey,
convo,
groupPk,
@@ -745,7 +641,7 @@ async function handleMemberAddedFromUI({
diff: { type: 'add', added: withoutHistory, withHistory: false },
...shared,
});
- const groupChange = await getWithoutHistoryControlMessage({
+ const groupChange = await GroupUpdateMessageFactory.getWithoutHistoryControlMessage({
adminSecretKey: group.secretKey,
convo,
groupPk,
@@ -866,7 +762,7 @@ async function handleMemberRemovedFromUI({
},
markAlreadySent: false, // the store below will mark the message as sent using dbMsgIdentifier
});
- removedControlMessage = await getRemovedControlMessage({
+ removedControlMessage = await GroupUpdateMessageFactory.getRemovedControlMessage({
adminSecretKey: group.secretKey,
convo,
groupPk,
diff --git a/ts/test/session/unit/crypto/SnodeSignatures_test.ts b/ts/test/session/unit/crypto/SnodeSignatures_test.ts
index b8bac771f..c28ff1902 100644
--- a/ts/test/session/unit/crypto/SnodeSignatures_test.ts
+++ b/ts/test/session/unit/crypto/SnodeSignatures_test.ts
@@ -7,11 +7,11 @@ import { getSodiumNode } from '../../../../node/sodiumNode';
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
import { SnodeGroupSignature } from '../../../../session/apis/snode_api/signature/groupSignature';
import { SnodeSignature } from '../../../../session/apis/snode_api/signature/snodeSignatures';
-import { WithSignature } from '../../../../session/apis/snode_api/types';
import { concatUInt8Array } from '../../../../session/crypto';
import { UserUtils } from '../../../../session/utils';
import { fromBase64ToArray, fromHexToArray } from '../../../../session/utils/String';
import { NetworkTime } from '../../../../util/NetworkTime';
+import { WithSignature } from '../../../../session/types/with';
use(chaiAsPromised);