feat: add toggle to share history or not with invite group v2

pull/2963/head
Audric Ackermann 1 year ago
parent 392e243b08
commit 8d0bd84ef0

@ -128,10 +128,8 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public componentDidUpdate(prevProps: Props, _prevState: State) {
const {
selectedConversationKey: newConversationKey,
selectedConversation: newConversation,
} = this.props;
const { selectedConversationKey: newConversationKey, selectedConversation: newConversation } =
this.props;
const { selectedConversationKey: oldConversationKey } = prevProps;
// if the convo is valid, and it changed, register for drag events

@ -145,15 +145,8 @@ export const MessageContent = (props: Props) => {
return null;
}
const {
direction,
text,
timestamp,
serverTimestamp,
previews,
quote,
attachments,
} = contentProps;
const { direction, text, timestamp, serverTimestamp, previews, quote, attachments } =
contentProps;
const hasContentBeforeAttachment = !isEmpty(previews) || !isEmpty(quote) || !isEmpty(text);

@ -21,7 +21,9 @@ type Props = {
const StyledMessageReactBar = styled.div`
background-color: var(--emoji-reaction-bar-background-color);
border-radius: 25px;
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.2), 0 0px 20px 0 rgba(0, 0, 0, 0.19);
box-shadow:
0 2px 16px 0 rgba(0, 0, 0, 0.2),
0 0px 20px 0 rgba(0, 0, 0, 0.19);
padding: 4px 8px;
white-space: nowrap;

@ -125,14 +125,8 @@ export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) =
return null;
}
const {
messageId,
direction,
receivedAt,
isUnread,
expirationDurationMs,
expirationTimestamp,
} = selected;
const { messageId, direction, receivedAt, isUnread, expirationDurationMs, expirationTimestamp } =
selected;
// NOTE we want messages on the left in the message detail view regardless of direction
const isIncoming = props.isDetailView ? true : direction === 'incoming';

@ -44,8 +44,8 @@ export const InteractionNotification = (props: PropsForInteractionNotification)
text = isCommunity
? window.i18n('leaveCommunityFailedPleaseTryAgain')
: isGroup
? window.i18n('leaveGroupFailedPleaseTryAgain')
: window.i18n('deleteConversationFailedPleaseTryAgain');
? window.i18n('leaveGroupFailedPleaseTryAgain')
: window.i18n('deleteConversationFailedPleaseTryAgain');
break;
default:
assertUnreachable(

@ -172,8 +172,8 @@ export const OverlayDisappearingMessages = () => {
{singleMode === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterReadSubtitle')
: singleMode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: window.i18n('settingAppliesToYourMessages')}
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: window.i18n('settingAppliesToYourMessages')}
</HeaderSubtitle>
</Header>
<DisappearingModes
@ -194,8 +194,8 @@ export const OverlayDisappearingMessages = () => {
singleMode
? disappearingModeOptions[singleMode]
: modeSelected
? disappearingModeOptions[modeSelected]
: undefined
? disappearingModeOptions[modeSelected]
: undefined
}
/>
</>
@ -217,8 +217,8 @@ export const OverlayDisappearingMessages = () => {
singleMode
? disappearingModeOptions[singleMode]
: modeSelected
? disappearingModeOptions[modeSelected]
: undefined
? disappearingModeOptions[modeSelected]
: undefined
}
dataTestId={'disappear-set-button'}
>

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import useKey from 'react-use/lib/useKey';
import { PubkeyType } from 'libsession_util_nodejs';
@ -25,10 +25,12 @@ import { SessionUtilUserGroups } from '../../session/utils/libsession/libsession
import { groupInfoActions } from '../../state/ducks/metaGroups';
import { useContactsToInviteToGroup } from '../../state/selectors/conversations';
import { useMemberGroupChangePending } from '../../state/selectors/groups';
import { useSelectedIsGroupV2 } from '../../state/selectors/selectedConversation';
import { MemberListItem } from '../MemberListItem';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionToggle } from '../basic/SessionToggle';
type Props = {
conversationId: string;
@ -122,6 +124,8 @@ const InviteContactsDialogInner = (props: Props) => {
const membersFromRedux = useSortedGroupMembers(conversationId);
const zombiesFromRedux = useZombies(conversationId);
const displayName = useConversationUsername(conversationId);
const isGroupV2 = useSelectedIsGroupV2();
const [shareHistory, setShareHistory] = useState(false);
const { uniqueValues: selectedContacts, addTo, removeFrom } = useSet<string>();
@ -149,9 +153,10 @@ const InviteContactsDialogInner = (props: Props) => {
void submitForOpenGroup(conversationId, selectedContacts);
} else {
if (PubKey.is03Pubkey(conversationId)) {
const forcedAsPubkeys = selectedContacts as Array<PubkeyType>;
const action = groupInfoActions.currentDeviceGroupMembersChange({
addMembersWithoutHistory: selectedContacts as Array<PubkeyType>,
addMembersWithHistory: [],
addMembersWithoutHistory: shareHistory ? [] : forcedAsPubkeys,
addMembersWithHistory: shareHistory ? forcedAsPubkeys : [],
removeMembers: [],
groupPk: conversationId,
});
@ -184,7 +189,12 @@ const InviteContactsDialogInner = (props: Props) => {
return (
<SessionWrapperModal title={titleText} onClose={closeDialog}>
<SpacerLG />
{isGroupV2 && (
<span style={{ display: 'flex', alignItems: 'center' }}>
Share History?{' '}
<SessionToggle active={shareHistory} onClick={() => setShareHistory(!shareHistory)} />
</span>
)}
<div className="contact-selection-list">
{hasContacts ? (
validContactsForInvite.map((member: string) => (
@ -208,7 +218,6 @@ const InviteContactsDialogInner = (props: Props) => {
<SpacerLG />
<SessionSpinner loading={isProcessingUIChange} />
<SpacerLG />
<div className="session-modal__button-group">
<SessionButton
text={okText}

File diff suppressed because one or more lines are too long

@ -174,12 +174,8 @@ const setupTheme = async () => {
// Do this only if we created a new Session ID, or if we already received the initial configuration message
const triggerSyncIfNeeded = async () => {
const us = UserUtils.getOurPubKeyStrFromCache();
await ConvoHub.use()
.get(us)
.setDidApproveMe(true, true);
await ConvoHub.use()
.get(us)
.setIsApproved(true, true);
await ConvoHub.use().get(us).setDidApproveMe(true, true);
await ConvoHub.use().get(us).setIsApproved(true, true);
const didWeHandleAConfigurationMessageAlready =
(await Data.getItemById(SettingsKey.hasSyncedInitialConfigurationItem))?.value || false;
if (didWeHandleAConfigurationMessageAlready) {

@ -352,28 +352,29 @@ export function useIsTyping(conversationId?: string): boolean {
return useConversationPropsById(conversationId)?.isTyping || false;
}
const getMessageExpirationProps = createSelector(getMessagePropsByMessageId, (props):
| PropsForExpiringMessage
| undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const getMessageExpirationProps = createSelector(
getMessagePropsByMessageId,
(props): PropsForExpiringMessage | undefined => {
if (!props || isEmpty(props)) {
return undefined;
}
const msgProps: PropsForExpiringMessage = {
...pick(props.propsForMessage, [
'convoId',
'direction',
'receivedAt',
'isUnread',
'expirationTimestamp',
'expirationDurationMs',
'isExpired',
]),
messageId: props.propsForMessage.id,
};
return msgProps;
});
const msgProps: PropsForExpiringMessage = {
...pick(props.propsForMessage, [
'convoId',
'direction',
'receivedAt',
'isUnread',
'expirationTimestamp',
'expirationDurationMs',
'isExpired',
]),
messageId: props.propsForMessage.id,
};
return msgProps;
}
);
export function useMessageExpirationPropsById(messageId?: string) {
return useSelector((state: StateType) => {
@ -430,9 +431,7 @@ export function useTimerOptionsByMode(disappearingMessageMode?: string, hasOnlyO
}, [disappearingMessageMode, hasOnlyOneMode]);
}
export function useQuoteAuthorName(
authorId?: string
): {
export function useQuoteAuthorName(authorId?: string): {
authorName: string | undefined;
isMe: boolean;
} {
@ -442,8 +441,8 @@ export function useQuoteAuthorName(
const authorName = isMe
? window.i18n('you')
: convoProps?.nickname || convoProps?.isPrivate
? convoProps?.displayNameInProfile
: undefined;
? convoProps?.displayNameInProfile
: undefined;
return { authorName, isMe };
}
@ -495,12 +494,12 @@ export function useDisappearingMessageSettingText({
expirationMode === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterRead')
: expirationMode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSend')
: expirationMode === 'legacy'
? isMe || (isGroup && !isPublic)
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeAfterRead')
: null;
: expirationMode === 'legacy'
? isMe || (isGroup && !isPublic)
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeAfterRead')
: null;
const expireTimerText = isNumber(expireTimer)
? abbreviate

@ -206,9 +206,7 @@ export const declineConversationWithConfirm = ({
const okKey: LocalizerKeys = alsoBlock ? 'block' : 'delete';
const nameToBlock =
alsoBlock && !!conversationIdOrigin
? ConvoHub.use()
.get(conversationIdOrigin)
?.getContactProfileNameOrShortenedPubKey()
? ConvoHub.use().get(conversationIdOrigin)?.getContactProfileNameOrShortenedPubKey()
: null;
const messageKey: LocalizerKeys = isGroupV2
? alsoBlock && nameToBlock
@ -621,9 +619,7 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
} else {
// this is a reupload. no need to generate a new profileKey
const ourConvoProfileKey =
ConvoHub.use()
.get(UserUtils.getOurPubKeyStrFromCache())
?.getProfileKey() || null;
ConvoHub.use().get(UserUtils.getOurPubKeyStrFromCache())?.getProfileKey() || null;
profileKey = ourConvoProfileKey ? fromHexToArray(ourConvoProfileKey) : null;
if (!profileKey) {

@ -2243,8 +2243,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const interactionNotification = lastMessageModel.getInteractionNotification();
const lastMessageInteractionType = interactionNotification?.interactionType;
const lastMessageInteractionStatus = lastMessageModel.getInteractionNotification()
?.interactionStatus;
const lastMessageInteractionStatus =
lastMessageModel.getInteractionNotification()?.interactionStatus;
const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined;
const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined;
// we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText`

@ -487,7 +487,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
if (groupUpdate.joinedWithHistory?.length) {
const change: PropsForGroupUpdateAdd = {
type: 'add',
added: groupUpdate.joined as Array<PubkeyType>,
added: groupUpdate.joinedWithHistory as Array<PubkeyType>,
withHistory: true,
};
return { change, ...sharedProps };
@ -763,9 +763,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview'));
const { hasAttachments, hasVisualMediaAttachments, hasFileAttachments } = getAttachmentMetadata(
this
);
const { hasAttachments, hasVisualMediaAttachments, hasFileAttachments } =
getAttachmentMetadata(this);
this.set({ hasAttachments, hasVisualMediaAttachments, hasFileAttachments });
await this.commit();
@ -816,8 +815,9 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
}
window.log.info(
`Upload of message data for message ${this.idForLogging()} is finished in ${Date.now() -
start}ms.`
`Upload of message data for message ${this.idForLogging()} is finished in ${
Date.now() - start
}ms.`
);
return {
body,
@ -1266,24 +1266,34 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const left: Array<string> | undefined = Array.isArray(groupUpdate.left)
? groupUpdate.left
: groupUpdate.left
? [groupUpdate.left]
: undefined;
? [groupUpdate.left]
: undefined;
const kicked: Array<string> | undefined = Array.isArray(groupUpdate.kicked)
? groupUpdate.kicked
: groupUpdate.kicked
? [groupUpdate.kicked]
: undefined;
? [groupUpdate.kicked]
: undefined;
const joined: Array<string> | undefined = Array.isArray(groupUpdate.joined)
? groupUpdate.joined
: groupUpdate.joined
? [groupUpdate.joined]
: undefined;
? [groupUpdate.joined]
: undefined;
const joinedWithHistory: Array<string> | undefined = Array.isArray(
groupUpdate.joinedWithHistory
)
? groupUpdate.joinedWithHistory
: groupUpdate.joinedWithHistory
? [groupUpdate.joinedWithHistory]
: undefined;
const forcedArrayUpdate: MessageGroupUpdate = {};
if (left) {
forcedArrayUpdate.left = left;
}
if (joinedWithHistory) {
forcedArrayUpdate.joinedWithHistory = joinedWithHistory;
}
if (joined) {
forcedArrayUpdate.joined = joined;
}
@ -1404,8 +1414,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return isCommunity
? window.i18n('leaveCommunityFailed')
: isGroup
? window.i18n('leaveGroupFailed')
: window.i18n('deleteConversationFailed');
? window.i18n('leaveGroupFailed')
: window.i18n('deleteConversationFailed');
default:
assertUnreachable(
interactionType,

@ -94,7 +94,6 @@ function decodeBatchRequest(snodeResponse: SnodeResponse): NotEmptyArrayOfBatchR
try {
// console.error('decodeBatch: ', snodeResponse);
if (snodeResponse.status !== 200) {
debugger;
throw new Error(`decodeBatchRequest invalid status code: ${snodeResponse.status}`);
}
const parsed = JSON.parse(snodeResponse.body);

@ -257,6 +257,8 @@ function toRole(namespace: number) {
return 'groupInfo';
case SnodeNamespaces.ClosedGroupMembers:
return 'groupMembers';
case SnodeNamespaces.ClosedGroupRevokedRetrievableMessages:
return 'groupRevoked';
default:
return `${namespace}`;
}

@ -87,9 +87,11 @@ async function encryptOnionV4RequestForPubkey(
) {
const plaintext = encodeV4Request(requestInfo);
return callUtilsWorker('encryptForPubkey', pubKeyX25519hex, plaintext) as Promise<
DestinationContext
>;
return callUtilsWorker(
'encryptForPubkey',
pubKeyX25519hex,
plaintext
) as Promise<DestinationContext>;
}
// Returns the actual ciphertext, symmetric key that will be used
// for decryption, and an ephemeral_key to send to the next hop
@ -99,9 +101,11 @@ async function encryptForPubKey(
): Promise<DestinationContext> {
const plaintext = new TextEncoder().encode(JSON.stringify(requestInfo));
return callUtilsWorker('encryptForPubkey', pubKeyX25519hex, plaintext) as Promise<
DestinationContext
>;
return callUtilsWorker(
'encryptForPubkey',
pubKeyX25519hex,
plaintext
) as Promise<DestinationContext>;
}
export type DestinationRelayV2 = {

@ -728,17 +728,16 @@ function retrieveItemWithNamespace(
): Array<RetrieveMessageItemWithNamespace> {
return flatten(
compact(
results.map(
result =>
result.messages.messages?.map(r => {
// throws if the result is not expected
const parsedItem = retrieveItemSchema.parse(r);
return {
...omit(parsedItem, 'timestamp'),
namespace: result.namespace,
storedAt: parsedItem.timestamp,
};
})
results.map(result =>
result.messages.messages?.map(r => {
// throws if the result is not expected
const parsedItem = retrieveItemSchema.parse(r);
return {
...omit(parsedItem, 'timestamp'),
namespace: result.namespace,
storedAt: parsedItem.timestamp,
};
})
)
)
);

@ -59,9 +59,7 @@ export async function destroyMessagesAndUpdateRedux(
// trigger a refresh the last message for all those uniq conversation
conversationWithChanges.forEach(convoIdToUpdate => {
ConvoHub.use()
.get(convoIdToUpdate)
?.updateLastMessage();
ConvoHub.use().get(convoIdToUpdate)?.updateLastMessage();
});
}
@ -91,12 +89,8 @@ async function destroyExpiredMessages() {
window.log.info('destroyExpiredMessages: convosToRefresh:', convosToRefresh);
await Promise.all(
convosToRefresh.map(async c => {
ConvoHub.use()
.get(c)
?.updateLastMessage();
return ConvoHub.use()
.get(c)
?.refreshInMemoryDetails();
ConvoHub.use().get(c)?.updateLastMessage();
return ConvoHub.use().get(c)?.refreshInMemoryDetails();
})
);
} catch (error) {
@ -278,9 +272,7 @@ function changeToDisappearingMessageType(
* This should only be used for DataExtractionNotification and CallMessages (the ones saved to the DB) currently.
* Note: this can only be called for private conversations, excluding ourselves as it throws otherwise (this wouldn't be right)
* */
function forcedDeleteAfterReadMsgSetting(
convo: ConversationModel
): {
function forcedDeleteAfterReadMsgSetting(convo: ConversationModel): {
expirationType: Exclude<DisappearingMessageType, 'deleteAfterSend'>;
expireTimer: number;
} {
@ -307,9 +299,7 @@ function forcedDeleteAfterReadMsgSetting(
* This should only be used for the outgoing CallMessages that we keep locally only (not synced, just the "you started a call" notification)
* Note: this can only be called for private conversations, excluding ourselves as it throws otherwise (this wouldn't be right)
* */
function forcedDeleteAfterSendMsgSetting(
convo: ConversationModel
): {
function forcedDeleteAfterSendMsgSetting(convo: ConversationModel): {
expirationType: Exclude<DisappearingMessageType, 'deleteAfterRead'>;
expireTimer: number;
} {
@ -365,7 +355,8 @@ async function checkForExpireUpdateInContentMessage(
): Promise<DisappearingMessageUpdate | undefined> {
const dataMessage = content.dataMessage as SignalService.DataMessage | undefined;
// We will only support legacy disappearing messages for a short period before disappearing messages v2 is unlocked
const isDisappearingMessagesV2Released = await ReleasedFeatures.checkIsDisappearMessageV2FeatureReleased();
const isDisappearingMessagesV2Released =
await ReleasedFeatures.checkIsDisappearMessageV2FeatureReleased();
const couldBeLegacyContentMessage = couldBeLegacyDisappearingMessageContent(content);
const isLegacyDataMessage =
@ -565,9 +556,9 @@ function getMessageReadyToDisappear(
if (msgExpirationWasAlreadyUpdated) {
const expirationStartTimestamp = messageExpirationFromRetrieve - expireTimer * 1000;
window.log.debug(
`incoming DaR message already read by another device, forcing readAt ${(Date.now() -
expirationStartTimestamp) /
1000}s ago, so with ${(messageExpirationFromRetrieve - Date.now()) / 1000}s left`
`incoming DaR message already read by another device, forcing readAt ${
(Date.now() - expirationStartTimestamp) / 1000
}s ago, so with ${(messageExpirationFromRetrieve - Date.now()) / 1000}s left`
);
messageModel.set({
expirationStartTimestamp,

@ -166,7 +166,11 @@ export async function addUpdateMessage({
if (diff.type === 'name' && diff.newName) {
groupUpdate.name = diff.newName;
} else if (diff.type === 'add' && diff.added) {
groupUpdate.joined = diff.added;
if (diff.withHistory) {
groupUpdate.joinedWithHistory = diff.added;
} else {
groupUpdate.joined = diff.added;
}
} else if (diff.type === 'left' && diff.left) {
groupUpdate.left = diff.left;
} else if (diff.type === 'kicked' && diff.kicked) {

@ -784,7 +784,7 @@ async function handleMemberAddedFromUI({
const updateMessagesToPush: Array<GroupUpdateMemberChangeMessage> = [];
if (withHistory.length) {
const msgModel = await ClosedGroup.addUpdateMessage({
diff: { type: 'add', added: withHistory, withHistory: false },
diff: { type: 'add', added: withHistory, withHistory: true },
...shared,
});
const groupChange = await getWithHistoryControlMessage({
@ -801,7 +801,7 @@ async function handleMemberAddedFromUI({
}
if (withoutHistory.length) {
const msgModel = await ClosedGroup.addUpdateMessage({
diff: { type: 'add', added: withoutHistory, withHistory: true },
diff: { type: 'add', added: withoutHistory, withHistory: false },
...shared,
});
const groupChange = await getWithoutHistoryControlMessage({

Loading…
Cancel
Save