fix: fixed a bunch of groupv2 chunk2 issues

pull/2963/head
Audric Ackermann 1 year ago
parent a83e44e183
commit d6d9bec5ba

@ -163,7 +163,7 @@ const GroupStatusText = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: Gro
} }
return ( return (
<StyledGroupStatusText <StyledGroupStatusText
data-testid={'group_member_status_text'} data-testid={'group-member-status-text'}
isFailure={groupPromotionFailed || groupInviteFailed} isFailure={groupPromotionFailed || groupInviteFailed}
> >
{statusText} {statusText}
@ -197,7 +197,7 @@ const ResendInviteButton = ({
}) => { }) => {
return ( return (
<SessionButton <SessionButton
dataTestId={'resend_invite_button'} dataTestId={'resend-invite-button'}
buttonShape={SessionButtonShape.Square} buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid} buttonType={SessionButtonType.Solid}
text={window.i18n('resend')} text={window.i18n('resend')}
@ -217,7 +217,7 @@ const ResendPromoteButton = ({
}) => { }) => {
return ( return (
<SessionButton <SessionButton
dataTestId={'resend_promote_button'} dataTestId={'resend-promote-button'}
buttonShape={SessionButtonShape.Square} buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid} buttonType={SessionButtonType.Solid}
buttonColor={SessionButtonColor.Danger} buttonColor={SessionButtonColor.Danger}
@ -271,7 +271,7 @@ export const MemberListItem = ({
margin="0 var(--margins-md)" margin="0 var(--margins-md)"
alignItems="flex-start" alignItems="flex-start"
> >
<StyledName data-testid={'group_member_name'}>{memberName}</StyledName> <StyledName data-testid={'group-member-name'}>{memberName}</StyledName>
<GroupStatusContainer <GroupStatusContainer
pubkey={pubkey} pubkey={pubkey}
displayGroupStatus={displayGroupStatus} displayGroupStatus={displayGroupStatus}

@ -96,8 +96,8 @@ export const ConversationMessageRequestButtons = () => {
<InvitedToGroupControlMessage /> <InvitedToGroupControlMessage />
<ConversationBannerRow> <ConversationBannerRow>
<SessionButton <SessionButton
onClick={async () => { onClick={() => {
await handleAcceptConversationRequest({ convoId: selectedConvoId, sendResponse: true }); void handleAcceptConversationRequest({ convoId: selectedConvoId });
}} }}
text={window.i18n('accept')} text={window.i18n('accept')}
dataTestId="accept-message-request" dataTestId="accept-message-request"

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { PubkeyType } from 'libsession_util_nodejs'; import { PubkeyType } from 'libsession_util_nodejs';
import { cloneDeep } from 'lodash';
import { useConversationsUsernameWithQuoteOrShortPk } from '../../../../hooks/useParamSelector'; import { useConversationsUsernameWithQuoteOrShortPk } from '../../../../hooks/useParamSelector';
import { arrayContainsUsOnly } from '../../../../models/message'; import { arrayContainsUsOnly } from '../../../../models/message';
import { PreConditionFailed } from '../../../../session/utils/errors'; import { PreConditionFailed } from '../../../../session/utils/errors';
@ -49,7 +50,10 @@ function moveUsToStart(
if (!usItem) { if (!usItem) {
throw new PreConditionFailed('"we" should have been there'); throw new PreConditionFailed('"we" should have been there');
} }
return { sortedWithUsFirst: [usItem, ...changed.slice(usAt, 1)] }; // deepClone because splice mutates the array
const changedCopy = cloneDeep(changed);
changedCopy.splice(usAt, 1);
return { sortedWithUsFirst: [usItem, ...changedCopy] };
} }
function changeOfMembersV2({ function changeOfMembersV2({
@ -84,10 +88,12 @@ function changeOfMembersV2({
: ('Removed' as const); : ('Removed' as const);
const key = `group${subject}${action}` as const; const key = `group${subject}${action}` as const;
return window.i18n( const sortedWithUsOrCount =
key, subject === 'Others'
sortedWithUsFirst.map(m => m.name) ? [sortedWithUsFirst[0].name, (sortedWithUsFirst.length - 1).toString()]
); : sortedWithUsFirst.map(m => m.name);
return window.i18n(key, sortedWithUsOrCount);
} }
// TODO those lookups might need to be memoized // TODO those lookups might need to be memoized

@ -31,6 +31,7 @@ import {
useSelectedIsActive, useSelectedIsActive,
useSelectedIsBlocked, useSelectedIsBlocked,
useSelectedIsGroupOrCommunity, useSelectedIsGroupOrCommunity,
useSelectedIsGroupV2,
useSelectedIsKickedFromGroup, useSelectedIsKickedFromGroup,
useSelectedIsPublic, useSelectedIsPublic,
useSelectedLastMessage, useSelectedLastMessage,
@ -125,13 +126,19 @@ const HeaderItem = () => {
const isBlocked = useSelectedIsBlocked(); const isBlocked = useSelectedIsBlocked();
const isKickedFromGroup = useSelectedIsKickedFromGroup(); const isKickedFromGroup = useSelectedIsKickedFromGroup();
const isGroup = useSelectedIsGroupOrCommunity(); const isGroup = useSelectedIsGroupOrCommunity();
const isGroupV2 = useSelectedIsGroupV2();
const isPublic = useSelectedIsPublic();
const subscriberCount = useSelectedSubscriberCount(); const subscriberCount = useSelectedSubscriberCount();
const weAreAdmin = useSelectedWeAreAdmin();
if (!selectedConvoKey) { if (!selectedConvoKey) {
return null; return null;
} }
const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked; const showInviteLegacyGroup =
!isPublic && !isGroupV2 && isGroup && !isKickedFromGroup && !isBlocked;
const showInviteGroupV2 = isGroupV2 && !isKickedFromGroup && !isBlocked && weAreAdmin;
const showInviteContacts = isPublic || showInviteLegacyGroup || showInviteGroupV2;
const showMemberCount = !!(subscriberCount && subscriberCount > 0); const showMemberCount = !!(subscriberCount && subscriberCount > 0);
return ( return (

@ -293,6 +293,7 @@ export const UpdateGroupMembersDialog = (props: Props) => {
onClick={onClickOK} onClick={onClickOK}
buttonType={SessionButtonType.Simple} buttonType={SessionButtonType.Simple}
disabled={isProcessingUIChange} disabled={isProcessingUIChange}
dataTestId="session-confirm-ok-button"
/> />
)} )}
<SessionButton <SessionButton
@ -301,6 +302,7 @@ export const UpdateGroupMembersDialog = (props: Props) => {
buttonType={SessionButtonType.Simple} buttonType={SessionButtonType.Simple}
onClick={closeDialog} onClick={closeDialog}
disabled={isProcessingUIChange} disabled={isProcessingUIChange}
dataTestId="session-confirm-cancel-button"
/> />
</div> </div>
</SessionWrapperModal> </SessionWrapperModal>

@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import styled from 'styled-components'; import styled from 'styled-components';
import { declineConversationWithoutConfirm } from '../../../interactions/conversationInteractions'; import { declineConversationWithoutConfirm } from '../../../interactions/conversationInteractions';
import { ed25519Str } from '../../../session/onions/onionPath';
import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/sync/syncUtils'; import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/sync/syncUtils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { resetLeftOverlayMode } from '../../../state/ducks/section'; import { resetLeftOverlayMode } from '../../../state/ducks/section';
@ -76,14 +77,20 @@ export const OverlayMessageRequest = () => {
for (let index = 0; index < messageRequests.length; index++) { for (let index = 0; index < messageRequests.length; index++) {
const convoId = messageRequests[index]; const convoId = messageRequests[index];
// eslint-disable-next-line no-await-in-loop try {
await declineConversationWithoutConfirm({ // eslint-disable-next-line no-await-in-loop
alsoBlock: false, await declineConversationWithoutConfirm({
conversationId: convoId, alsoBlock: false,
currentlySelectedConvo, conversationId: convoId,
syncToDevices: false, currentlySelectedConvo,
conversationIdOrigin: null, // block is false, no need for conversationIdOrigin syncToDevices: false,
}); conversationIdOrigin: null, // block is false, no need for conversationIdOrigin
});
} catch (e) {
window.log.warn(
`failed to decline msg request ${ed25519Str(convoId)} with error: ${e.message}`
);
}
} }
await forceSyncConfigurationNowIfNeeded(); await forceSyncConfigurationNowIfNeeded();

@ -450,7 +450,6 @@ export const AcceptMsgRequestMenuItem = () => {
onClick={async () => { onClick={async () => {
await handleAcceptConversationRequest({ await handleAcceptConversationRequest({
convoId, convoId,
sendResponse: true,
}); });
}} }}
> >

@ -47,6 +47,9 @@ export function useConversationUsername(convoId?: string) {
// So let's keep falling back to convoProps?.displayNameInProfile if groupName is not set yet (it comes later through the groupInfos namespace) // So let's keep falling back to convoProps?.displayNameInProfile if groupName is not set yet (it comes later through the groupInfos namespace)
return groupName; return groupName;
} }
if (convoId && (PubKey.is03Pubkey(convoId) || PubKey.is05Pubkey(convoId))) {
return convoProps?.nickname || convoProps?.displayNameInProfile || PubKey.shorten(convoId);
}
return convoProps?.nickname || convoProps?.displayNameInProfile || convoId; return convoProps?.nickname || convoProps?.displayNameInProfile || convoId;
} }

@ -4,7 +4,7 @@ import {
ConversationTypeEnum, ConversationTypeEnum,
READ_MESSAGE_STATE, READ_MESSAGE_STATE,
} from '../models/conversationAttributes'; } from '../models/conversationAttributes';
import { CallManager, SyncUtils, ToastUtils, UserUtils } from '../session/utils'; import { CallManager, PromiseUtils, SyncUtils, ToastUtils, UserUtils } from '../session/utils';
import { SessionButtonColor } from '../components/basic/SessionButton'; import { SessionButtonColor } from '../components/basic/SessionButton';
import { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings'; import { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings';
@ -113,26 +113,25 @@ export async function unblockConvoById(conversationId: string) {
await conversation.commit(); await conversation.commit();
} }
export const handleAcceptConversationRequest = async ({ export const handleAcceptConversationRequest = async ({ convoId }: { convoId: string }) => {
convoId,
sendResponse,
}: {
convoId: string;
sendResponse: boolean;
}) => {
const convo = ConvoHub.use().get(convoId); const convo = ConvoHub.use().get(convoId);
if (!convo) { if (!convo || (!convo.isPrivate() && !convo.isClosedGroupV2())) {
return null; return null;
} }
await convo.setDidApproveMe(true, false); const previousIsApproved = convo.isApproved();
const previousDidApprovedMe = convo.didApproveMe();
// Note: we don't mark as approvedMe = true, as we do not know if they did send us a message yet.
await convo.setIsApproved(true, false); await convo.setIsApproved(true, false);
await convo.commit(); await convo.commit();
void forceSyncConfigurationNowIfNeeded();
if (convo.isPrivate()) { if (convo.isPrivate()) {
await convo.addOutgoingApprovalMessage(Date.now()); // we only need the approval message (and sending a reply) when we are accepting a message request. i.e. someone sent us a message already and we didn't accept it yet.
if (sendResponse) { if (!previousIsApproved && previousDidApprovedMe) {
await convo.addOutgoingApprovalMessage(Date.now());
await convo.sendMessageRequestResponse(); await convo.sendMessageRequestResponse();
} }
return null; return null;
} }
if (PubKey.is03Pubkey(convoId)) { if (PubKey.is03Pubkey(convoId)) {
@ -143,12 +142,17 @@ export const handleAcceptConversationRequest = async ({
} }
// this updates the wrapper and refresh the redux slice // this updates the wrapper and refresh the redux slice
await UserGroupsWrapperActions.setGroup({ ...found, invitePending: false }); await UserGroupsWrapperActions.setGroup({ ...found, invitePending: false });
const acceptedPromise = new Promise(resolve => {
// nothing else to do (and especially not wait for first poll) when the convo was already approved
if (previousIsApproved) {
return null;
}
const pollAndSendResponsePromise = new Promise(resolve => {
getSwarmPollingInstance().addGroupId(convoId, async () => { getSwarmPollingInstance().addGroupId(convoId, async () => {
// we need to do a first poll to fetch the keys etc before we can send our invite response // we need to do a first poll to fetch the keys etc before we can send our invite response
// this is pretty hacky, but also an admin seeing a message from that user in the group will mark it as not pending anymore // this is pretty hacky, but also an admin seeing a message from that user in the group will mark it as not pending anymore
await sleepFor(2000); await sleepFor(2000);
if (sendResponse) { if (!previousIsApproved) {
await GroupV2Receiver.sendInviteResponseToGroup({ groupPk: convoId }); await GroupV2Receiver.sendInviteResponseToGroup({ groupPk: convoId });
} }
window.log.info( window.log.info(
@ -157,7 +161,17 @@ export const handleAcceptConversationRequest = async ({
return resolve(true); return resolve(true);
}); });
}); });
await acceptedPromise;
// try at most 10s for the keys, and everything to come before continuing processing.
// Note: this is important as otherwise the polling just hangs when sending a message to a group (as the cb in addGroupId() is never called back)
const timeout = 10000;
try {
await PromiseUtils.timeout(pollAndSendResponsePromise, timeout);
} catch (e) {
window.log.warn(
`handleAcceptConversationRequest: waited ${timeout}ms for first poll of group ${ed25519Str(convoId)} to happen, but timedout with: ${e.message}`
);
}
} }
return null; return null;
}; };

@ -17,6 +17,7 @@ import {
xor, xor,
} from 'lodash'; } from 'lodash';
import { DisappearingMessageConversationModeType } from 'libsession_util_nodejs';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { SignalService } from '../protobuf'; import { SignalService } from '../protobuf';
import { getMessageQueue } from '../session'; import { getMessageQueue } from '../session';
@ -29,7 +30,7 @@ import { PubKey } from '../session/types';
import { ToastUtils, UserUtils } from '../session/utils'; import { ToastUtils, UserUtils } from '../session/utils';
import { BlockedNumberController } from '../util'; import { BlockedNumberController } from '../util';
import { MessageModel } from './message'; import { MessageModel } from './message';
import { MessageAttributesOptionals, MessageDirection } from './messageType'; import { MessageAttributesOptionals } from './messageType';
import { Data } from '../data/data'; import { Data } from '../data/data';
import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/opengroupV2/ApiUtil'; import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/opengroupV2/ApiUtil';
@ -81,7 +82,6 @@ import { UserSync } from '../session/utils/job_runners/jobs/UserSyncJob';
import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts'; import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
import { SessionUtilConvoInfoVolatile } from '../session/utils/libsession/libsession_utils_convo_info_volatile'; import { SessionUtilConvoInfoVolatile } from '../session/utils/libsession/libsession_utils_convo_info_volatile';
import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups'; import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups';
import { forceSyncConfigurationNowIfNeeded } from '../session/utils/sync/syncUtils';
import { getOurProfile } from '../session/utils/User'; import { getOurProfile } from '../session/utils/User';
import { import {
deleteExternalFilesOfConversation, deleteExternalFilesOfConversation,
@ -129,7 +129,6 @@ import {
import { handleAcceptConversationRequest } from '../interactions/conversationInteractions'; import { handleAcceptConversationRequest } from '../interactions/conversationInteractions';
import { DisappearingMessages } from '../session/disappearing_messages'; import { DisappearingMessages } from '../session/disappearing_messages';
import { DisappearingMessageConversationModeType } from '../session/disappearing_messages/types';
import { GroupUpdateInfoChangeMessage } from '../session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInfoChangeMessage'; import { GroupUpdateInfoChangeMessage } from '../session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInfoChangeMessage';
import { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob'; import { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob';
import { UpdateMsgExpirySwarm } from '../session/utils/job_runners/jobs/UpdateMsgExpirySwarmJob'; import { UpdateMsgExpirySwarm } from '../session/utils/job_runners/jobs/UpdateMsgExpirySwarmJob';
@ -150,7 +149,7 @@ type InMemoryConvoInfos = {
const inMemoryConvoInfos: Map<string, InMemoryConvoInfos> = new Map(); const inMemoryConvoInfos: Map<string, InMemoryConvoInfos> = new Map();
export class ConversationModel extends Backbone.Model<ConversationAttributes> { export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public updateLastMessage: () => unknown; // unknown because it is a Promise that we do not wait to await public updateLastMessage: () => unknown; // unknown because it is a Promise that we do not want to await
public throttledBumpTyping: () => void; public throttledBumpTyping: () => void;
public throttledNotify: (message: MessageModel) => void; public throttledNotify: (message: MessageModel) => void;
public markConversationRead: (opts: { public markConversationRead: (opts: {
@ -237,7 +236,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public isClosedGroup(): boolean { public isClosedGroup(): boolean {
return Boolean( return Boolean(
(this.get('type') === ConversationTypeEnum.GROUP && this.id.startsWith('05')) || (this.get('type') === ConversationTypeEnum.GROUP && PubKey.is05Pubkey(this.id)) ||
this.isClosedGroupV2() this.isClosedGroupV2()
); );
} }
@ -254,7 +253,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return this.isPrivate() && PubKey.isBlinded(this.id); return this.isPrivate() && PubKey.isBlinded(this.id);
} }
// returns true if this is a closed/medium or open group /**
* @returns true if this is a legacy, closed or community
*/
public isGroup() { public isGroup() {
return isOpenOrClosedGroup(this.get('type')); return isOpenOrClosedGroup(this.get('type'));
} }
@ -298,6 +299,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
public getPriority() { public getPriority() {
if (PubKey.is05Pubkey(this.id) && this.isPrivate()) {
// TODO once we have a libsession state, we can make this used accross the app without repeating as much
// if a private chat, trust the value from the Libsession wrapper cached first
const contact = SessionUtilContact.getContactCached(this.id);
if (contact) {
return contact.priority;
}
}
return this.get('priority') || CONVERSATION_PRIORITIES.default; return this.get('priority') || CONVERSATION_PRIORITIES.default;
} }
@ -325,8 +334,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.priority = priorityFromDb; toRet.priority = priorityFromDb;
} }
if (this.get('markedAsUnread')) { if (this.isMarkedUnread()) {
toRet.isMarkedUnread = !!this.get('markedAsUnread'); toRet.isMarkedUnread = this.isMarkedUnread();
} }
const blocksSogsMsgReqsTimestamp = this.get('blocksSogsMsgReqsTimestamp'); const blocksSogsMsgReqsTimestamp = this.get('blocksSogsMsgReqsTimestamp');
@ -380,17 +389,17 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.getRealSessionUsername()) { if (this.getRealSessionUsername()) {
toRet.displayNameInProfile = this.getRealSessionUsername(); toRet.displayNameInProfile = this.getRealSessionUsername();
} }
if (this.get('nickname')) { if (this.getNickname()) {
toRet.nickname = this.get('nickname'); toRet.nickname = this.getNickname();
} }
if (BlockedNumberController.isBlocked(this.id)) { if (BlockedNumberController.isBlocked(this.id)) {
toRet.isBlocked = true; toRet.isBlocked = true;
} }
if (this.get('didApproveMe')) { if (this.didApproveMe()) {
toRet.didApproveMe = this.get('didApproveMe'); toRet.didApproveMe = this.didApproveMe();
} }
if (this.get('isApproved')) { if (this.isApproved()) {
toRet.isApproved = this.get('isApproved'); toRet.isApproved = this.isApproved();
} }
if (this.getExpireTimer()) { if (this.getExpireTimer()) {
toRet.expireTimer = this.getExpireTimer(); toRet.expireTimer = this.getExpireTimer();
@ -601,32 +610,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
expireTimer, expireTimer,
}; };
const shouldApprove = !this.isApproved() && this.isPrivate();
const incomingMessageCount = await Data.getMessageCountByType(
this.id,
MessageDirection.incoming
);
const hasIncomingMessages = incomingMessageCount > 0;
if (PubKey.isBlinded(this.id)) { if (PubKey.isBlinded(this.id)) {
window.log.info('Sending a blinded message react to this user: ', this.id); window.log.info('Sending a blinded message react to this user: ', this.id);
await this.sendBlindedMessageRequest(chatMessageParams); await this.sendBlindedMessageRequest(chatMessageParams);
return; return;
} }
if (shouldApprove) { // handleAcceptConversationRequest will take care of sending response depending on the type of conversation, if needed
await this.setIsApproved(true); await handleAcceptConversationRequest({
if (hasIncomingMessages) { convoId: this.id,
// have to manually add approval for local client here as DB conditional approval check in config msg handling will prevent this from running });
await this.addOutgoingApprovalMessage(Date.now());
if (!this.didApproveMe()) {
await this.setDidApproveMe(true);
}
// should only send once
await this.sendMessageRequestResponse();
void forceSyncConfigurationNowIfNeeded();
}
}
if (this.isOpenGroupV2()) { if (this.isOpenGroupV2()) {
// communities have no expiration timer support, so enforce it here. // communities have no expiration timer support, so enforce it here.
@ -739,7 +732,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
/** /**
* When you have accepted another users message request * When you have accepted another users message request
* @param timestamp for determining the order for this message to appear like a regular message * Note: you shouldn't need to use this directly. Instead use `handleAcceptConversationRequest()`
*/ */
public async addOutgoingApprovalMessage(timestamp: number) { public async addOutgoingApprovalMessage(timestamp: number) {
await this.addSingleOutgoingMessage({ await this.addSingleOutgoingMessage({
@ -772,8 +765,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
/** /**
* Sends an accepted message request response. * Sends an accepted message request response to a private chat
* Currently, we never send anything for denied message requests. * Currently, we never send anything for denied message requests.
* Note: you souldn't to use this directly. Instead use `handleAcceptConversationRequest()`
*/ */
public async sendMessageRequestResponse() { public async sendMessageRequestResponse() {
if (!this.isPrivate()) { if (!this.isPrivate()) {
@ -1547,7 +1541,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public async setIsApproved(value: boolean, shouldCommit: boolean = true) { public async setIsApproved(value: boolean, shouldCommit: boolean = true) {
const valueForced = Boolean(value); const valueForced = Boolean(value);
if (!this.isPrivate()) { if (!this.isPrivate() && !this.isClosedGroupV2()) {
return; return;
} }
@ -1752,11 +1746,20 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
public didApproveMe() { public didApproveMe() {
return Boolean(this.get('didApproveMe')); if (PubKey.is05Pubkey(this.id) && this.isPrivate()) {
// if a private chat, trust the value from the Libsession wrapper cached first
// TODO once we have a libsession state, we can make this used accross the app without repeating as much
return SessionUtilContact.getContactCached(this.id)?.approvedMe ?? !!this.get('didApproveMe');
}
return !!this.get('didApproveMe');
} }
public isApproved() { public isApproved() {
return Boolean(this.get('isApproved')); if (PubKey.is05Pubkey(this.id) && this.isPrivate()) {
// if a private chat, trust the value from the Libsession wrapper cached first
return SessionUtilContact.getContactCached(this.id)?.approved ?? !!this.get('isApproved');
}
return !!this.get('isApproved');
} }
/** /**
@ -2035,37 +2038,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
lokiProfile: UserUtils.getOurProfile(), lokiProfile: UserUtils.getOurProfile(),
}; };
const shouldApprove = !this.isApproved() && (this.isPrivate() || this.isClosedGroupV2());
const incomingMessageCount = await Data.getMessageCountByType(
this.id,
MessageDirection.incoming
);
const hasIncomingMessages = incomingMessageCount > 0;
if (PubKey.isBlinded(this.id)) { if (PubKey.isBlinded(this.id)) {
window.log.info('Sending a blinded message to this user: ', this.id); window.log.info('Sending a blinded message to this user: ', this.id);
await this.sendBlindedMessageRequest(chatMessageParams); await this.sendBlindedMessageRequest(chatMessageParams);
return; return;
} }
if (shouldApprove) { // handleAcceptConversationRequest will take care of sending response depending on the type of conversation
await handleAcceptConversationRequest({ await handleAcceptConversationRequest({
convoId: this.id, convoId: this.id,
sendResponse: !message, });
});
await this.setIsApproved(true);
if (hasIncomingMessages) {
// have to manually add approval for local client here as DB conditional approval check in config msg handling will prevent this from running
await this.addOutgoingApprovalMessage(Date.now());
if (!this.didApproveMe()) {
await this.setDidApproveMe(true);
}
// should only send once
await this.sendMessageRequestResponse();
void forceSyncConfigurationNowIfNeeded();
}
}
if (this.isOpenGroupV2()) { if (this.isOpenGroupV2()) {
const chatMessageOpenGroupV2 = new OpenGroupVisibleMessage(chatMessageParams); const chatMessageOpenGroupV2 = new OpenGroupVisibleMessage(chatMessageParams);
@ -2262,20 +2244,19 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined; const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined;
const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined; const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined;
// we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText` // we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText`
const lastMessageUpdate = const lastMessageUpdate = !isEmpty(lastMessageNotificationText)
!!lastMessageNotificationText && !isEmpty(lastMessageNotificationText) ? {
? { lastMessage: lastMessageNotificationText || '',
lastMessage: lastMessageNotificationText || '', lastMessageStatus,
lastMessageStatus, lastMessageInteractionType,
lastMessageInteractionType, lastMessageInteractionStatus,
lastMessageInteractionStatus, }
} : {
: { lastMessage: '',
lastMessage: '', lastMessageStatus: undefined,
lastMessageStatus: undefined, lastMessageInteractionType: undefined,
lastMessageInteractionType: undefined, lastMessageInteractionStatus: undefined,
lastMessageInteractionStatus: undefined, };
};
const existingLastMessageInteractionType = this.get('lastMessageInteractionType'); const existingLastMessageInteractionType = this.get('lastMessageInteractionType');
const existingLastMessageInteractionStatus = this.get('lastMessageInteractionStatus'); const existingLastMessageInteractionStatus = this.get('lastMessageInteractionStatus');
@ -2444,7 +2425,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
) { ) {
return false; return false;
} }
return Boolean(this.get('isApproved')); return this.isApproved();
} }
private async bumpTyping() { private async bumpTyping() {

@ -120,6 +120,18 @@ export function arrayContainsOneItemOnly(arrayToCheck: Array<string> | undefined
return arrayToCheck && arrayToCheck.length === 1; return arrayToCheck && arrayToCheck.length === 1;
} }
function formatJoined(joined: Array<string>) {
const names = joined.map(ConvoHub.use().getContactProfileNameOrShortenedPubKey);
const messages = [];
if (names.length > 1) {
messages.push(window.i18n('multipleJoinedTheGroup', [names.join(', ')]));
} else {
messages.push(window.i18n('joinedTheGroup', names));
}
return messages.join(' ');
}
export class MessageModel extends Backbone.Model<MessageAttributes> { export class MessageModel extends Backbone.Model<MessageAttributes> {
constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) { constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) {
const filledAttrs = fillMessageAttributesWithDefaults(attributes); const filledAttrs = fillMessageAttributesWithDefaults(attributes);
@ -1328,15 +1340,11 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
if (groupUpdate.joined && groupUpdate.joined.length) { if (groupUpdate.joined && groupUpdate.joined.length) {
const names = groupUpdate.joined.map(ConvoHub.use().getContactProfileNameOrShortenedPubKey); return formatJoined(groupUpdate.joined);
const messages = []; }
if (names.length > 1) { if (groupUpdate.joinedWithHistory && groupUpdate.joinedWithHistory.length) {
messages.push(window.i18n('multipleJoinedTheGroup', [names.join(', ')])); return formatJoined(groupUpdate.joinedWithHistory);
} else {
messages.push(window.i18n('joinedTheGroup', names));
}
return messages.join(' ');
} }
if (groupUpdate.kicked && groupUpdate.kicked.length) { if (groupUpdate.kicked && groupUpdate.kicked.length) {

11
ts/react.d.ts vendored

@ -7,8 +7,7 @@ import 'react';
declare module 'react' { declare module 'react' {
type SessionDataTestId = type SessionDataTestId =
| 'group_member_status_text' | 'group-member-status-text'
| 'group_member_name'
| 'loading-spinner' | 'loading-spinner'
| 'session-toast' | 'session-toast'
| 'loading-animation' | 'loading-animation'
@ -17,7 +16,6 @@ declare module 'react' {
| 'chooser-new-group' | 'chooser-new-group'
| 'chooser-new-conversation-button' | 'chooser-new-conversation-button'
| 'new-conversation-button' | 'new-conversation-button'
| 'module-conversation__user__profile-name'
| 'message-request-banner' | 'message-request-banner'
| 'leftpane-section-container' | 'leftpane-section-container'
| 'group-name-input' | 'group-name-input'
@ -164,14 +162,13 @@ declare module 'react' {
| 'continue-session-button' | 'continue-session-button'
| 'next-new-conversation-button' | 'next-new-conversation-button'
| 'reveal-recovery-phrase' | 'reveal-recovery-phrase'
| 'resend_invite_button' | 'resend-invite-button'
| 'session-confirm-cancel-button' | 'session-confirm-cancel-button'
| 'session-confirm-ok-button' | 'session-confirm-ok-button'
| 'confirm-nickname' | 'confirm-nickname'
| 'path-light-svg' | 'path-light-svg'
| 'group_member_status_text' | 'group-member-name'
| 'group_member_name' | 'resend-promote-button'
| 'resend_promote_button'
| 'next-button' | 'next-button'
| 'save-button-profile-update' | 'save-button-profile-update'
| 'save-button-profile-update' | 'save-button-profile-update'

@ -20,6 +20,7 @@ import { concatUInt8Array, getSodiumRenderer } from '../session/crypto';
import { removeMessagePadding } from '../session/crypto/BufferPadding'; import { removeMessagePadding } from '../session/crypto/BufferPadding';
import { DisappearingMessages } from '../session/disappearing_messages'; import { DisappearingMessages } from '../session/disappearing_messages';
import { ReadyToDisappearMsgUpdate } from '../session/disappearing_messages/types'; import { ReadyToDisappearMsgUpdate } from '../session/disappearing_messages/types';
import { ed25519Str } from '../session/onions/onionPath';
import { ProfileManager } from '../session/profile_manager/ProfileManager'; import { ProfileManager } from '../session/profile_manager/ProfileManager';
import { GroupUtils, UserUtils } from '../session/utils'; import { GroupUtils, UserUtils } from '../session/utils';
import { perfEnd, perfStart } from '../session/utils/Performance'; import { perfEnd, perfStart } from '../session/utils/Performance';
@ -722,12 +723,7 @@ async function handleMessageRequestResponse(
envelope: EnvelopePlus, envelope: EnvelopePlus,
messageRequestResponse: SignalService.MessageRequestResponse messageRequestResponse: SignalService.MessageRequestResponse
) { ) {
const { isApproved } = messageRequestResponse; if (!messageRequestResponse || !messageRequestResponse.isApproved) {
if (!isApproved) {
await IncomingMessageCache.removeFromCache(envelope);
return;
}
if (!messageRequestResponse) {
window?.log?.error('handleMessageRequestResponse: Invalid parameters -- dropping message.'); window?.log?.error('handleMessageRequestResponse: Invalid parameters -- dropping message.');
await IncomingMessageCache.removeFromCache(envelope); await IncomingMessageCache.removeFromCache(envelope);
return; return;
@ -738,6 +734,14 @@ async function handleMessageRequestResponse(
const convosToMerge = findCachedBlindedMatchOrLookupOnAllServers(envelope.source, sodium); const convosToMerge = findCachedBlindedMatchOrLookupOnAllServers(envelope.source, sodium);
const unblindedConvoId = envelope.source; const unblindedConvoId = envelope.source;
if (!PubKey.is05Pubkey(unblindedConvoId)) {
window?.log?.warn(
'handleMessageRequestResponse: Invalid unblindedConvoId -- dropping message.'
);
await IncomingMessageCache.removeFromCache(envelope);
return;
}
const conversationToApprove = await ConvoHub.use().getOrCreateAndWait( const conversationToApprove = await ConvoHub.use().getOrCreateAndWait(
unblindedConvoId, unblindedConvoId,
ConversationTypeEnum.PRIVATE ConversationTypeEnum.PRIVATE
@ -747,12 +751,14 @@ async function handleMessageRequestResponse(
mostRecentActiveAt = toNumber(envelope.timestamp); mostRecentActiveAt = toNumber(envelope.timestamp);
} }
const previousApprovedMe = conversationToApprove.didApproveMe();
await conversationToApprove.setDidApproveMe(true, false);
conversationToApprove.set({ conversationToApprove.set({
active_at: mostRecentActiveAt, active_at: mostRecentActiveAt,
isApproved: true,
didApproveMe: true,
}); });
await conversationToApprove.unhideIfNeeded(false); await conversationToApprove.unhideIfNeeded(false);
await conversationToApprove.commit();
if (convosToMerge.length) { if (convosToMerge.length) {
// merge fields we care by hand // merge fields we care by hand
@ -809,23 +815,21 @@ async function handleMessageRequestResponse(
); );
} }
if (!conversationToApprove || conversationToApprove.didApproveMe()) { if (previousApprovedMe) {
await conversationToApprove?.commit(); await conversationToApprove.commit();
window?.log?.info(
'Conversation already contains the correct value for the didApproveMe field.' window.log.inf(
`convo ${ed25519Str(conversationToApprove.id)} previousApprovedMe is already true. Nothing to do `
); );
await IncomingMessageCache.removeFromCache(envelope); await IncomingMessageCache.removeFromCache(envelope);
return; return;
} }
await conversationToApprove.setDidApproveMe(true, true);
// Conversation was not approved before so a sync is needed // Conversation was not approved before so a sync is needed
await conversationToApprove.addIncomingApprovalMessage( await conversationToApprove.addIncomingApprovalMessage(
toNumber(envelope.timestamp), toNumber(envelope.timestamp),
unblindedConvoId unblindedConvoId
); );
await IncomingMessageCache.removeFromCache(envelope); await IncomingMessageCache.removeFromCache(envelope);
} }

@ -253,10 +253,10 @@ async function retrieveNextMessagesNoRetries(
GetNetworkTime.handleTimestampOffsetFromNetwork('retrieve', bodyFirstResult.t); GetNetworkTime.handleTimestampOffsetFromNetwork('retrieve', bodyFirstResult.t);
// merge results with their corresponding namespaces // merge results with their corresponding namespaces
return results.map((result, index) => ({ return namespacesAndLastHashes.map((n, index) => ({
code: result.code, code: results[index].code,
messages: result.body as RetrieveMessagesResultsContent, messages: results[index].body as RetrieveMessagesResultsContent,
namespace: namespacesAndLastHashes[index].namespace, namespace: n.namespace,
})); }));
} catch (e) { } catch (e) {
window?.log?.warn('exception while parsing json of nextMessage:', e); window?.log?.warn('exception while parsing json of nextMessage:', e);

@ -144,6 +144,11 @@ export class SwarmPolling {
if (this.groupPolling.findIndex(m => m.pubkey.key === pk.key) === -1) { if (this.groupPolling.findIndex(m => m.pubkey.key === pk.key) === -1) {
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pk.key); window?.log?.info('Swarm addGroupId: adding pubkey to polling', pk.key);
this.groupPolling.push({ pubkey: pk, lastPolledTimestamp: 0, callbackFirstPoll }); this.groupPolling.push({ pubkey: pk, lastPolledTimestamp: 0, callbackFirstPoll });
} else if (callbackFirstPoll) {
// group is already polled. Hopefully we already have keys for it to decrypt messages?
void sleepFor(2000).then(() => {
void callbackFirstPoll();
});
} }
} }
@ -547,7 +552,7 @@ export class SwarmPolling {
} }
results = results.slice(0, results.length - 1); results = results.slice(0, results.length - 1);
} }
console.warn('results what when we get kicked out?: ', results); // console.warn('results what when we get kicked out?: ', results); // debugger
const lastMessages = results.map(r => { const lastMessages = results.map(r => {
return last(r.messages.messages); return last(r.messages.messages);
}); });
@ -845,7 +850,6 @@ async function handleMessagesForGroupV2(
throw new Error('decryptForGroupV2 returned empty envelope'); throw new Error('decryptForGroupV2 returned empty envelope');
} }
console.warn('envelopePlus', envelopePlus);
// this is the processing of the message itself, which can be long. // this is the processing of the message itself, which can be long.
// We allow 1 minute per message at most, which should be plenty // We allow 1 minute per message at most, which should be plenty
await Receiver.handleSwarmContentDecryptedWithTimeout({ await Receiver.handleSwarmContentDecryptedWithTimeout({

@ -111,6 +111,10 @@ export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
), ),
}); });
if (type === Type.ADDED && this.typeOfChange === 'addedWithHistory') {
memberChangeMessage.historyShared = true;
}
return new SignalService.DataMessage({ groupUpdateMessage: { memberChangeMessage } }); return new SignalService.DataMessage({ groupUpdateMessage: { memberChangeMessage } });
} }

@ -23,9 +23,8 @@ async function updateOurProfileSync({ displayName, profileUrl, profileKey, prior
} }
await updateProfileOfContact(us, displayName, profileUrl, profileKey); await updateProfileOfContact(us, displayName, profileUrl, profileKey);
if (priority !== null && ourConvo.getPriority() !== priority) { if (priority !== null) {
ourConvo.set('priority', priority); await ourConvo.setPriorityFromWrapper(priority, true);
await ourConvo.commit();
} }
} }

@ -368,9 +368,11 @@ export class MessageQueue {
'sendSingleMessageAndHandleResult: failed to send message with: ', 'sendSingleMessageAndHandleResult: failed to send message with: ',
error.message error.message
); );
if (rawMessage) { await MessageSentHandler.handleSwarmMessageSentFailure(
await MessageSentHandler.handleSwarmMessageSentFailure(rawMessage, error); { device: rawMessage.device, identifier: rawMessage.identifier },
} error
);
return null; return null;
} finally { } finally {
// Remove from the cache because retrying is done in the sender // Remove from the cache because retrying is done in the sender

@ -376,14 +376,13 @@ async function sendMessagesDataToSnode(
revokeSubRequest, revokeSubRequest,
unrevokeSubRequest, unrevokeSubRequest,
]); ]);
const targetNode = await SnodePool.getNodeFromSwarmOrThrow(asssociatedWith); const targetNode = await SnodePool.getNodeFromSwarmOrThrow(asssociatedWith);
try { try {
const storeResults = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries( const storeResults = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries(
rawRequests, rawRequests,
targetNode, targetNode,
4000, 6000,
asssociatedWith, asssociatedWith,
method method
); );
@ -395,6 +394,11 @@ async function sendMessagesDataToSnode(
); );
throw new Error('doUnsignedSnodeBatchRequestNoRetries: Invalid result'); throw new Error('doUnsignedSnodeBatchRequestNoRetries: Invalid result');
} }
await handleBatchResultWithSubRequests({
batchResult: storeResults,
subRequests: rawRequests,
destination: asssociatedWith,
});
const firstResult = storeResults[0]; const firstResult = storeResults[0];
@ -545,6 +549,9 @@ async function encryptMessagesAndWrap(
/** /**
* Send an array of preencrypted data to the corresponding swarm. * Send an array of preencrypted data to the corresponding swarm.
* Warning:
* This does not handle result of messages and marking messages as read, syncing them currently.
* For this, use the `MessageQueue.sendSingleMessage()` for now.
* *
* @param params the data to deposit * @param params the data to deposit
* @param destination the pubkey we should deposit those message to * @param destination the pubkey we should deposit those message to
@ -700,6 +707,10 @@ export const MessageSender = {
signSubRequests, signSubRequests,
}; };
/**
* Note: this function does not handle the syncing logic of messages yet.
* Use it to push message to group, to note to self, or with user messages which do not require a syncing logic
*/
async function handleBatchResultWithSubRequests({ async function handleBatchResultWithSubRequests({
batchResult, batchResult,
destination, destination,
@ -756,7 +767,21 @@ async function handleBatchResultWithSubRequests({
const foundMessage = await Data.getMessageById(subRequest.dbMessageIdentifier); const foundMessage = await Data.getMessageById(subRequest.dbMessageIdentifier);
if (foundMessage) { if (foundMessage) {
await foundMessage.updateMessageHash(storedHash); await foundMessage.updateMessageHash(storedHash);
// - a message pushed to a group is always synced
// - a message sent to ourself when it was a marked as sentSync is a synced message to ourself
if (
isDestinationClosedGroup ||
(subRequest.destination === us && foundMessage.get('sentSync'))
) {
foundMessage.set({ synced: true });
}
foundMessage.set({
sent_to: [subRequest.destination],
sent: true,
sent_at: storedAt,
});
await foundMessage.commit(); await foundMessage.commit();
await foundMessage.getConversation()?.updateLastMessage();
window?.log?.info(`updated message ${foundMessage.get('id')} with hash: ${storedHash}`); window?.log?.info(`updated message ${foundMessage.get('id')} with hash: ${storedHash}`);
} }
/* eslint-enable no-await-in-loop */ /* eslint-enable no-await-in-loop */

@ -147,7 +147,10 @@ async function handleSwarmMessageSentSuccess(
fetchedMessage.getConversation()?.updateLastMessage(); fetchedMessage.getConversation()?.updateLastMessage();
} }
async function handleSwarmMessageSentFailure(sentMessage: OutgoingRawMessage, error: any) { async function handleSwarmMessageSentFailure(
sentMessage: Pick<OutgoingRawMessage, 'device' | 'identifier'>,
error: any
) {
const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier); const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
if (!fetchedMessage) { if (!fetchedMessage) {
return; return;
@ -157,14 +160,12 @@ async function handleSwarmMessageSentFailure(sentMessage: OutgoingRawMessage, er
await fetchedMessage.saveErrors(error); await fetchedMessage.saveErrors(error);
} }
if (!(sentMessage instanceof OpenGroupVisibleMessage)) { const isOurDevice = UserUtils.isUsFromCache(sentMessage.device);
const isOurDevice = UserUtils.isUsFromCache(sentMessage.device); // if this message was for ourself, and it was not already synced,
// if this message was for ourself, and it was not already synced, // it means that we failed to sync it.
// it means that we failed to sync it. // so just remove the flag saying that we are currently sending the sync message
// so just remove the flag saying that we are currently sending the sync message if (isOurDevice && !fetchedMessage.get('sync')) {
if (isOurDevice && !fetchedMessage.get('sync')) { fetchedMessage.set({ sentSync: false });
fetchedMessage.set({ sentSync: false });
}
} }
// always mark the message as sent. // always mark the message as sent.

@ -533,7 +533,7 @@ export async function USER_callRecipient(recipient: string) {
weAreCallerOnCurrentCall = true; weAreCallerOnCurrentCall = true;
// initiating a call is analogous to sending a message request // initiating a call is analogous to sending a message request
await handleAcceptConversationRequest({ convoId: recipient, sendResponse: false }); await handleAcceptConversationRequest({ convoId: recipient });
// Note: we do the sending of the preoffer manually as the sendTo1o1NonDurably rely on having a message saved to the db for MessageSentSuccess // Note: we do the sending of the preoffer manually as the sendTo1o1NonDurably rely on having a message saved to the db for MessageSentSuccess
// which is not the case for a pre offer message (the message only exists in memory) // which is not the case for a pre offer message (the message only exists in memory)
@ -934,7 +934,6 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
// consider the conversation completely approved // consider the conversation completely approved
await handleAcceptConversationRequest({ await handleAcceptConversationRequest({
convoId: fromSender, convoId: fromSender,
sendResponse: true,
}); });
} }

@ -136,7 +136,6 @@ const initNewGroupInWrapper = createAsyncThunk(
throw new Error('groupSecretKey was empty just after creation.'); throw new Error('groupSecretKey was empty just after creation.');
} }
newGroup.name = groupName; // this will be used by the linked devices until they fetch the info from the groups swarm newGroup.name = groupName; // this will be used by the linked devices until they fetch the info from the groups swarm
// the `GroupSync` below will need the secretKey of the group to be saved in the wrapper. So save it! // the `GroupSync` below will need the secretKey of the group to be saved in the wrapper. So save it!
await UserGroupsWrapperActions.setGroup(newGroup); await UserGroupsWrapperActions.setGroup(newGroup);
const ourEd25519KeypairBytes = await UserUtils.getUserED25519KeyPairBytes(); const ourEd25519KeypairBytes = await UserUtils.getUserED25519KeyPairBytes();
@ -183,8 +182,7 @@ const initNewGroupInWrapper = createAsyncThunk(
const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2); const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2);
await convo.setIsApproved(true, false); await convo.setIsApproved(true, false);
await convo.commit(); // commit here too, as the poll needs it to be approved
console.warn('updateMessages for new group might need an update message?');
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({ const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk, groupPk,
@ -196,6 +194,36 @@ const initNewGroupInWrapper = createAsyncThunk(
window.log.warn('GroupSync.pushChangesToGroupSwarmIfNeeded during create failed'); window.log.warn('GroupSync.pushChangesToGroupSwarmIfNeeded during create failed');
throw new Error('failed to pushChangesToGroupSwarmIfNeeded'); throw new Error('failed to pushChangesToGroupSwarmIfNeeded');
} }
// push one group change message were initial members are added to the group
if (membersFromWrapper.length) {
const membersHex = uniq(membersFromWrapper.map(m => m.pubkeyHex));
const sentAt = GetNetworkTime.now();
const msgModel = await ClosedGroup.addUpdateMessage({
diff: { type: 'add', added: membersHex, withHistory: false },
expireUpdate: null,
sender: us,
sentAt,
convo,
});
const groupChange = await getWithoutHistoryControlMessage({
adminSecretKey: groupSecretKey,
convo,
groupPk,
withoutHistory: membersHex,
createAtNetworkTimestamp: sentAt,
dbMsgIdentifier: msgModel.id,
});
if (groupChange) {
await GroupSync.storeGroupUpdateMessages({
groupPk,
updateMessages: [groupChange],
});
}
}
await convo.commit();
getSwarmPollingInstance().addGroupId(new PubKey(groupPk)); getSwarmPollingInstance().addGroupId(new PubKey(groupPk));
await convo.unhideIfNeeded(); await convo.unhideIfNeeded();
@ -838,7 +866,6 @@ async function handleMemberAddedFromUI({
if (groupChange) { if (groupChange) {
updateMessagesToPush.push(groupChange); updateMessagesToPush.push(groupChange);
} }
console.warn(`diff: { type: ' should add case for addWithHistory here ?`);
} }
await convo.commit(); await convo.commit();

Loading…
Cancel
Save