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

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

@ -1,6 +1,7 @@
import React from 'react';
import { PubkeyType } from 'libsession_util_nodejs';
import { cloneDeep } from 'lodash';
import { useConversationsUsernameWithQuoteOrShortPk } from '../../../../hooks/useParamSelector';
import { arrayContainsUsOnly } from '../../../../models/message';
import { PreConditionFailed } from '../../../../session/utils/errors';
@ -49,7 +50,10 @@ function moveUsToStart(
if (!usItem) {
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({
@ -84,10 +88,12 @@ function changeOfMembersV2({
: ('Removed' as const);
const key = `group${subject}${action}` as const;
return window.i18n(
key,
sortedWithUsFirst.map(m => m.name)
);
const sortedWithUsOrCount =
subject === 'Others'
? [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

@ -31,6 +31,7 @@ import {
useSelectedIsActive,
useSelectedIsBlocked,
useSelectedIsGroupOrCommunity,
useSelectedIsGroupV2,
useSelectedIsKickedFromGroup,
useSelectedIsPublic,
useSelectedLastMessage,
@ -125,13 +126,19 @@ const HeaderItem = () => {
const isBlocked = useSelectedIsBlocked();
const isKickedFromGroup = useSelectedIsKickedFromGroup();
const isGroup = useSelectedIsGroupOrCommunity();
const isGroupV2 = useSelectedIsGroupV2();
const isPublic = useSelectedIsPublic();
const subscriberCount = useSelectedSubscriberCount();
const weAreAdmin = useSelectedWeAreAdmin();
if (!selectedConvoKey) {
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);
return (

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

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

@ -450,7 +450,6 @@ export const AcceptMsgRequestMenuItem = () => {
onClick={async () => {
await handleAcceptConversationRequest({
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)
return groupName;
}
if (convoId && (PubKey.is03Pubkey(convoId) || PubKey.is05Pubkey(convoId))) {
return convoProps?.nickname || convoProps?.displayNameInProfile || PubKey.shorten(convoId);
}
return convoProps?.nickname || convoProps?.displayNameInProfile || convoId;
}

@ -4,7 +4,7 @@ import {
ConversationTypeEnum,
READ_MESSAGE_STATE,
} 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 { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings';
@ -113,26 +113,25 @@ export async function unblockConvoById(conversationId: string) {
await conversation.commit();
}
export const handleAcceptConversationRequest = async ({
convoId,
sendResponse,
}: {
convoId: string;
sendResponse: boolean;
}) => {
export const handleAcceptConversationRequest = async ({ convoId }: { convoId: string }) => {
const convo = ConvoHub.use().get(convoId);
if (!convo) {
if (!convo || (!convo.isPrivate() && !convo.isClosedGroupV2())) {
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.commit();
void forceSyncConfigurationNowIfNeeded();
if (convo.isPrivate()) {
await convo.addOutgoingApprovalMessage(Date.now());
if (sendResponse) {
// 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 (!previousIsApproved && previousDidApprovedMe) {
await convo.addOutgoingApprovalMessage(Date.now());
await convo.sendMessageRequestResponse();
}
return null;
}
if (PubKey.is03Pubkey(convoId)) {
@ -143,12 +142,17 @@ export const handleAcceptConversationRequest = async ({
}
// this updates the wrapper and refresh the redux slice
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 () => {
// 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
await sleepFor(2000);
if (sendResponse) {
if (!previousIsApproved) {
await GroupV2Receiver.sendInviteResponseToGroup({ groupPk: convoId });
}
window.log.info(
@ -157,7 +161,17 @@ export const handleAcceptConversationRequest = async ({
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;
};

@ -17,6 +17,7 @@ import {
xor,
} from 'lodash';
import { DisappearingMessageConversationModeType } from 'libsession_util_nodejs';
import { v4 } from 'uuid';
import { SignalService } from '../protobuf';
import { getMessageQueue } from '../session';
@ -29,7 +30,7 @@ import { PubKey } from '../session/types';
import { ToastUtils, UserUtils } from '../session/utils';
import { BlockedNumberController } from '../util';
import { MessageModel } from './message';
import { MessageAttributesOptionals, MessageDirection } from './messageType';
import { MessageAttributesOptionals } from './messageType';
import { Data } from '../data/data';
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 { SessionUtilConvoInfoVolatile } from '../session/utils/libsession/libsession_utils_convo_info_volatile';
import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups';
import { forceSyncConfigurationNowIfNeeded } from '../session/utils/sync/syncUtils';
import { getOurProfile } from '../session/utils/User';
import {
deleteExternalFilesOfConversation,
@ -129,7 +129,6 @@ import {
import { handleAcceptConversationRequest } from '../interactions/conversationInteractions';
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 { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob';
import { UpdateMsgExpirySwarm } from '../session/utils/job_runners/jobs/UpdateMsgExpirySwarmJob';
@ -150,7 +149,7 @@ type InMemoryConvoInfos = {
const inMemoryConvoInfos: Map<string, InMemoryConvoInfos> = new Map();
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 throttledNotify: (message: MessageModel) => void;
public markConversationRead: (opts: {
@ -237,7 +236,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public isClosedGroup(): boolean {
return Boolean(
(this.get('type') === ConversationTypeEnum.GROUP && this.id.startsWith('05')) ||
(this.get('type') === ConversationTypeEnum.GROUP && PubKey.is05Pubkey(this.id)) ||
this.isClosedGroupV2()
);
}
@ -254,7 +253,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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() {
return isOpenOrClosedGroup(this.get('type'));
}
@ -298,6 +299,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
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;
}
@ -325,8 +334,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.priority = priorityFromDb;
}
if (this.get('markedAsUnread')) {
toRet.isMarkedUnread = !!this.get('markedAsUnread');
if (this.isMarkedUnread()) {
toRet.isMarkedUnread = this.isMarkedUnread();
}
const blocksSogsMsgReqsTimestamp = this.get('blocksSogsMsgReqsTimestamp');
@ -380,17 +389,17 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.getRealSessionUsername()) {
toRet.displayNameInProfile = this.getRealSessionUsername();
}
if (this.get('nickname')) {
toRet.nickname = this.get('nickname');
if (this.getNickname()) {
toRet.nickname = this.getNickname();
}
if (BlockedNumberController.isBlocked(this.id)) {
toRet.isBlocked = true;
}
if (this.get('didApproveMe')) {
toRet.didApproveMe = this.get('didApproveMe');
if (this.didApproveMe()) {
toRet.didApproveMe = this.didApproveMe();
}
if (this.get('isApproved')) {
toRet.isApproved = this.get('isApproved');
if (this.isApproved()) {
toRet.isApproved = this.isApproved();
}
if (this.getExpireTimer()) {
toRet.expireTimer = this.getExpireTimer();
@ -601,32 +610,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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)) {
window.log.info('Sending a blinded message react to this user: ', this.id);
await this.sendBlindedMessageRequest(chatMessageParams);
return;
}
if (shouldApprove) {
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();
}
}
// handleAcceptConversationRequest will take care of sending response depending on the type of conversation, if needed
await handleAcceptConversationRequest({
convoId: this.id,
});
if (this.isOpenGroupV2()) {
// 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
* @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) {
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.
* Note: you souldn't to use this directly. Instead use `handleAcceptConversationRequest()`
*/
public async sendMessageRequestResponse() {
if (!this.isPrivate()) {
@ -1547,7 +1541,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public async setIsApproved(value: boolean, shouldCommit: boolean = true) {
const valueForced = Boolean(value);
if (!this.isPrivate()) {
if (!this.isPrivate() && !this.isClosedGroupV2()) {
return;
}
@ -1752,11 +1746,20 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
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() {
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(),
};
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)) {
window.log.info('Sending a blinded message to this user: ', this.id);
await this.sendBlindedMessageRequest(chatMessageParams);
return;
}
if (shouldApprove) {
await handleAcceptConversationRequest({
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();
}
}
// handleAcceptConversationRequest will take care of sending response depending on the type of conversation
await handleAcceptConversationRequest({
convoId: this.id,
});
if (this.isOpenGroupV2()) {
const chatMessageOpenGroupV2 = new OpenGroupVisibleMessage(chatMessageParams);
@ -2262,20 +2244,19 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined;
const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined;
// we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText`
const lastMessageUpdate =
!!lastMessageNotificationText && !isEmpty(lastMessageNotificationText)
? {
lastMessage: lastMessageNotificationText || '',
lastMessageStatus,
lastMessageInteractionType,
lastMessageInteractionStatus,
}
: {
lastMessage: '',
lastMessageStatus: undefined,
lastMessageInteractionType: undefined,
lastMessageInteractionStatus: undefined,
};
const lastMessageUpdate = !isEmpty(lastMessageNotificationText)
? {
lastMessage: lastMessageNotificationText || '',
lastMessageStatus,
lastMessageInteractionType,
lastMessageInteractionStatus,
}
: {
lastMessage: '',
lastMessageStatus: undefined,
lastMessageInteractionType: undefined,
lastMessageInteractionStatus: undefined,
};
const existingLastMessageInteractionType = this.get('lastMessageInteractionType');
const existingLastMessageInteractionStatus = this.get('lastMessageInteractionStatus');
@ -2444,7 +2425,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
) {
return false;
}
return Boolean(this.get('isApproved'));
return this.isApproved();
}
private async bumpTyping() {

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

11
ts/react.d.ts vendored

@ -7,8 +7,7 @@ import 'react';
declare module 'react' {
type SessionDataTestId =
| 'group_member_status_text'
| 'group_member_name'
| 'group-member-status-text'
| 'loading-spinner'
| 'session-toast'
| 'loading-animation'
@ -17,7 +16,6 @@ declare module 'react' {
| 'chooser-new-group'
| 'chooser-new-conversation-button'
| 'new-conversation-button'
| 'module-conversation__user__profile-name'
| 'message-request-banner'
| 'leftpane-section-container'
| 'group-name-input'
@ -164,14 +162,13 @@ declare module 'react' {
| 'continue-session-button'
| 'next-new-conversation-button'
| 'reveal-recovery-phrase'
| 'resend_invite_button'
| 'resend-invite-button'
| 'session-confirm-cancel-button'
| 'session-confirm-ok-button'
| 'confirm-nickname'
| 'path-light-svg'
| 'group_member_status_text'
| 'group_member_name'
| 'resend_promote_button'
| 'group-member-name'
| 'resend-promote-button'
| 'next-button'
| '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 { DisappearingMessages } from '../session/disappearing_messages';
import { ReadyToDisappearMsgUpdate } from '../session/disappearing_messages/types';
import { ed25519Str } from '../session/onions/onionPath';
import { ProfileManager } from '../session/profile_manager/ProfileManager';
import { GroupUtils, UserUtils } from '../session/utils';
import { perfEnd, perfStart } from '../session/utils/Performance';
@ -722,12 +723,7 @@ async function handleMessageRequestResponse(
envelope: EnvelopePlus,
messageRequestResponse: SignalService.MessageRequestResponse
) {
const { isApproved } = messageRequestResponse;
if (!isApproved) {
await IncomingMessageCache.removeFromCache(envelope);
return;
}
if (!messageRequestResponse) {
if (!messageRequestResponse || !messageRequestResponse.isApproved) {
window?.log?.error('handleMessageRequestResponse: Invalid parameters -- dropping message.');
await IncomingMessageCache.removeFromCache(envelope);
return;
@ -738,6 +734,14 @@ async function handleMessageRequestResponse(
const convosToMerge = findCachedBlindedMatchOrLookupOnAllServers(envelope.source, sodium);
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(
unblindedConvoId,
ConversationTypeEnum.PRIVATE
@ -747,12 +751,14 @@ async function handleMessageRequestResponse(
mostRecentActiveAt = toNumber(envelope.timestamp);
}
const previousApprovedMe = conversationToApprove.didApproveMe();
await conversationToApprove.setDidApproveMe(true, false);
conversationToApprove.set({
active_at: mostRecentActiveAt,
isApproved: true,
didApproveMe: true,
});
await conversationToApprove.unhideIfNeeded(false);
await conversationToApprove.commit();
if (convosToMerge.length) {
// merge fields we care by hand
@ -809,23 +815,21 @@ async function handleMessageRequestResponse(
);
}
if (!conversationToApprove || conversationToApprove.didApproveMe()) {
await conversationToApprove?.commit();
window?.log?.info(
'Conversation already contains the correct value for the didApproveMe field.'
if (previousApprovedMe) {
await conversationToApprove.commit();
window.log.inf(
`convo ${ed25519Str(conversationToApprove.id)} previousApprovedMe is already true. Nothing to do `
);
await IncomingMessageCache.removeFromCache(envelope);
return;
}
await conversationToApprove.setDidApproveMe(true, true);
// Conversation was not approved before so a sync is needed
await conversationToApprove.addIncomingApprovalMessage(
toNumber(envelope.timestamp),
unblindedConvoId
);
await IncomingMessageCache.removeFromCache(envelope);
}

@ -253,10 +253,10 @@ async function retrieveNextMessagesNoRetries(
GetNetworkTime.handleTimestampOffsetFromNetwork('retrieve', bodyFirstResult.t);
// merge results with their corresponding namespaces
return results.map((result, index) => ({
code: result.code,
messages: result.body as RetrieveMessagesResultsContent,
namespace: namespacesAndLastHashes[index].namespace,
return namespacesAndLastHashes.map((n, index) => ({
code: results[index].code,
messages: results[index].body as RetrieveMessagesResultsContent,
namespace: n.namespace,
}));
} catch (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) {
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pk.key);
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);
}
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 => {
return last(r.messages.messages);
});
@ -845,7 +850,6 @@ async function handleMessagesForGroupV2(
throw new Error('decryptForGroupV2 returned empty envelope');
}
console.warn('envelopePlus', envelopePlus);
// this is the processing of the message itself, which can be long.
// We allow 1 minute per message at most, which should be plenty
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 } });
}

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

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

@ -376,14 +376,13 @@ async function sendMessagesDataToSnode(
revokeSubRequest,
unrevokeSubRequest,
]);
const targetNode = await SnodePool.getNodeFromSwarmOrThrow(asssociatedWith);
try {
const storeResults = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries(
rawRequests,
targetNode,
4000,
6000,
asssociatedWith,
method
);
@ -395,6 +394,11 @@ async function sendMessagesDataToSnode(
);
throw new Error('doUnsignedSnodeBatchRequestNoRetries: Invalid result');
}
await handleBatchResultWithSubRequests({
batchResult: storeResults,
subRequests: rawRequests,
destination: asssociatedWith,
});
const firstResult = storeResults[0];
@ -545,6 +549,9 @@ async function encryptMessagesAndWrap(
/**
* 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 destination the pubkey we should deposit those message to
@ -700,6 +707,10 @@ export const MessageSender = {
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({
batchResult,
destination,
@ -756,7 +767,21 @@ async function handleBatchResultWithSubRequests({
const foundMessage = await Data.getMessageById(subRequest.dbMessageIdentifier);
if (foundMessage) {
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.getConversation()?.updateLastMessage();
window?.log?.info(`updated message ${foundMessage.get('id')} with hash: ${storedHash}`);
}
/* eslint-enable no-await-in-loop */

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

@ -533,7 +533,7 @@ export async function USER_callRecipient(recipient: string) {
weAreCallerOnCurrentCall = true;
// 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
// 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
await handleAcceptConversationRequest({
convoId: fromSender,
sendResponse: true,
});
}

@ -136,7 +136,6 @@ const initNewGroupInWrapper = createAsyncThunk(
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
// the `GroupSync` below will need the secretKey of the group to be saved in the wrapper. So save it!
await UserGroupsWrapperActions.setGroup(newGroup);
const ourEd25519KeypairBytes = await UserUtils.getUserED25519KeyPairBytes();
@ -183,8 +182,7 @@ const initNewGroupInWrapper = createAsyncThunk(
const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2);
await convo.setIsApproved(true, false);
console.warn('updateMessages for new group might need an update message?');
await convo.commit(); // commit here too, as the poll needs it to be approved
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
@ -196,6 +194,36 @@ const initNewGroupInWrapper = createAsyncThunk(
window.log.warn('GroupSync.pushChangesToGroupSwarmIfNeeded during create failed');
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));
await convo.unhideIfNeeded();
@ -838,7 +866,6 @@ async function handleMemberAddedFromUI({
if (groupChange) {
updateMessagesToPush.push(groupChange);
}
console.warn(`diff: { type: ' should add case for addWithHistory here ?`);
}
await convo.commit();

Loading…
Cancel
Save