Merge branch 'unstable' into userconfig_disappearingmessage

pull/2971/head
William Grant 2 years ago
commit 2a05185138

@ -2,7 +2,7 @@
"name": "session-desktop", "name": "session-desktop",
"productName": "Session", "productName": "Session",
"description": "Private messaging from your desktop", "description": "Private messaging from your desktop",
"version": "1.11.1", "version": "1.11.2",
"license": "GPL-3.0", "license": "GPL-3.0",
"author": { "author": {
"name": "Oxen Labs", "name": "Oxen Labs",
@ -109,7 +109,7 @@
"protobufjs": "^7.2.4", "protobufjs": "^7.2.4",
"rc-slider": "^10.2.1", "rc-slider": "^10.2.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-contexify": "5.0.0", "react-contexify": "^6.0.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-h5-audio-player": "^3.2.0", "react-h5-audio-player": "^3.2.0",

@ -6,7 +6,7 @@ body.rtl {
.right-panel-item, .right-panel-item,
.contact-selection-list, .contact-selection-list,
.group-member-list__selection, .group-member-list__selection,
.react-contexify__item { .contexify_item {
direction: rtl; direction: rtl;
} }

@ -1,6 +1,6 @@
// Modules // Modules
@import 'react-h5-audio-player/lib/styles.css'; @import 'react-h5-audio-player/lib/styles.css';
@import 'react-contexify/dist/ReactContexify.min.css'; @import 'react-contexify/dist/ReactContexify.css'; // the .css is actually referencing the .min.css in the 6.0.0
@import 'react-toastify/dist/ReactToastify.css'; @import 'react-toastify/dist/ReactToastify.css';
@import 'sanitize.css/sanitize.css'; @import 'sanitize.css/sanitize.css';

@ -3,36 +3,35 @@ import styled from 'styled-components';
export const SessionContextMenuContainer = styled.div.attrs({ export const SessionContextMenuContainer = styled.div.attrs({
// custom props // custom props
})` })`
.react-contexify { .contexify {
// be sure it is more than the one set for the More Information screen of messages // be sure it is more than the one set for the More Information screen of messages
z-index: 30; z-index: 30;
min-width: 200px; min-width: 200px;
box-shadow: 0px 0px 10px var(--context-menu-shadow-color) !important; box-shadow: 0px 0px 10px var(--context-menu-shadow-color) !important;
background-color: var(--context-menu-background-color); background-color: var(--context-menu-background-color);
&.react-contexify__theme--dark { &.contexify_theme-dark {
background-color: var(--context-menu-background-color); background-color: var(--context-menu-background-color);
} }
.react-contexify__item { .contexify_item {
background: var(--context-menu-background-color); background: var(--context-menu-background-color);
} }
.react-contexify__item:not(.react-contexify__item--disabled):hover .contexify_item:not(.contexify_item-disabled):hover > .contexify_itemContent {
> .react-contexify__item__content {
background: var(--context-menu-background-hover-color); background: var(--context-menu-background-hover-color);
color: var(--context-menu-text-hover-color); color: var(--context-menu-text-hover-color);
} }
.react-contexify__item__content { .contexify_itemContent {
transition: var(--default-duration); transition: var(--default-duration);
color: var(--context-menu-text-color); color: var(--context-menu-text-color);
} }
&.react-contexify__submenu { &.contexify_submenu {
top: -28px !important; // height of an item element top: -28px !important; // height of an item element
} }
.react-contexify__submenu-arrow { .contexify_submenu-arrow {
line-height: 16px; // center the arrow for submenu line-height: 16px; // center the arrow for submenu
} }
} }

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { animation, contextMenu, Item, Menu } from 'react-contexify'; import { contextMenu, Item, Menu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
@ -20,7 +20,7 @@ const VideoInputMenu = ({
}) => { }) => {
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
{camerasList.map(m => { {camerasList.map(m => {
return ( return (
<Item <Item
@ -93,7 +93,7 @@ const AudioInputMenu = ({
}) => { }) => {
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
{audioInputsList.map(m => { {audioInputsList.map(m => {
return ( return (
<Item <Item
@ -162,7 +162,7 @@ const AudioOutputMenu = ({
}) => { }) => {
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
{audioOutputsList.map(m => { {audioOutputsList.map(m => {
return ( return (
<Item <Item

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-misused-promises */
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { animation, Item, Menu, useContextMenu } from 'react-contexify'; import { Item, ItemParams, Menu, useContextMenu } from 'react-contexify';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useClickAway, useMouse } from 'react-use'; import { useClickAway, useMouse } from 'react-use';
import styled from 'styled-components'; import styled from 'styled-components';
import { isNumber } from 'lodash';
import { Data } from '../../../../data/data'; import { Data } from '../../../../data/data';
import { MessageInteraction } from '../../../../interactions'; import { MessageInteraction } from '../../../../interactions';
@ -66,7 +66,7 @@ type Props = { messageId: string; contextMenuId: string; enableReactions: boolea
const StyledMessageContextMenu = styled.div` const StyledMessageContextMenu = styled.div`
position: relative; position: relative;
.react-contexify { .contexify {
margin-left: -104px; margin-left: -104px;
} }
`; `;
@ -103,49 +103,6 @@ const DeleteForEveryone = ({ messageId }: { messageId: string }) => {
type MessageId = { messageId: string }; type MessageId = { messageId: string };
const SaveAttachment = ({ messageId }: MessageId) => {
const convoId = useSelectedConversationKey();
const attachments = useMessageAttachments(messageId);
const timestamp = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const sender = useMessageSender(messageId);
const saveAttachment = useCallback(
(e: any) => {
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
let targetAttachmentIndex = e.triggerEvent.path[1].getAttribute('data-attachmentindex');
e.event.stopPropagation();
if (!attachments?.length || !convoId || !sender) {
return;
}
if (!targetAttachmentIndex) {
targetAttachmentIndex = 0;
}
if (targetAttachmentIndex > attachments.length) {
return;
}
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[targetAttachmentIndex],
messageTimestamp,
messageSender: sender,
conversationId: convoId,
});
},
[convoId, sender, attachments, serverTimestamp, timestamp]
);
if (!convoId) {
return null;
}
return attachments?.length ? (
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
) : null;
};
const AdminActionItems = ({ messageId }: MessageId) => { const AdminActionItems = ({ messageId }: MessageId) => {
const convoId = useSelectedConversationKey(); const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic(); const isPublic = useSelectedIsPublic();
@ -218,6 +175,11 @@ export const MessageContextMenu = (props: Props) => {
const status = useMessageStatus(messageId); const status = useMessageStatus(messageId);
const isDeletable = useMessageIsDeletable(messageId); const isDeletable = useMessageIsDeletable(messageId);
const text = useMessageBody(messageId); const text = useMessageBody(messageId);
const attachments = useMessageAttachments(messageId);
const timestamp = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const sender = useMessageSender(messageId);
const isOutgoing = direction === 'outgoing'; const isOutgoing = direction === 'outgoing';
const isSent = status === 'sent' || status === 'read'; // a read message should be replyable const isSent = status === 'sent' || status === 'read'; // a read message should be replyable
@ -233,21 +195,24 @@ export const MessageContextMenu = (props: Props) => {
const [mouseX, setMouseX] = useState(0); const [mouseX, setMouseX] = useState(0);
const [mouseY, setMouseY] = useState(0); const [mouseY, setMouseY] = useState(0);
const onContextMenuShown = () => { const onVisibilityChange = useCallback(
if (showEmojiPanel) { (isVisible: boolean) => {
setShowEmojiPanel(false); if (isVisible) {
} if (showEmojiPanel) {
window.contextMenuShown = true; setShowEmojiPanel(false);
}; }
window.contextMenuShown = true;
const onContextMenuHidden = useCallback(() => { return;
// This function will called before the click event }
// on the message would trigger (and I was unable to // This function will called before the click event
// prevent propagation in this case), so use a short timeout // on the message would trigger (and I was unable to
setTimeout(() => { // prevent propagation in this case), so use a short timeout
window.contextMenuShown = false; setTimeout(() => {
}, 100); window.contextMenuShown = false;
}, []); }, 100);
},
[showEmojiPanel]
);
const onShowDetail = async () => { const onShowDetail = async () => {
const found = await Data.getMessageById(messageId); const found = await Data.getMessageById(messageId);
@ -313,6 +278,30 @@ export const MessageContextMenu = (props: Props) => {
} }
}; };
const saveAttachment = (e: ItemParams) => {
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
// Note: If you change this, also make sure to update the `handleContextMenu()` in GenericReadableMessage.tsx
const targetAttachmentIndex = isNumber(e?.props?.dataAttachmentIndex)
? e.props.dataAttachmentIndex
: 0;
e.event.stopPropagation();
if (!attachments?.length || !convoId || !sender) {
return;
}
if (targetAttachmentIndex > attachments.length) {
return;
}
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[targetAttachmentIndex],
messageTimestamp,
messageSender: sender,
conversationId: convoId,
});
};
useClickAway(emojiPanelRef, () => { useClickAway(emojiPanelRef, () => {
onEmojiLoseFocus(); onEmojiLoseFocus();
}); });
@ -360,18 +349,14 @@ export const MessageContextMenu = (props: Props) => {
</StyledEmojiPanelContainer> </StyledEmojiPanelContainer>
)} )}
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu <Menu id={contextMenuId} onVisibilityChange={onVisibilityChange} animation="fade">
id={contextMenuId}
onShown={onContextMenuShown}
onHidden={onContextMenuHidden}
animation={animation.fade}
>
{enableReactions && ( {enableReactions && (
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
<MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} /> <MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} />
)} )}
<SaveAttachment messageId={messageId} /> {attachments?.length ? (
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
) : null}
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item> <Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
{(isSent || !isOutgoing) && ( {(isSent || !isOutgoing) && (
<Item onClick={onReply}>{window.i18n('replyToMessage')}</Item> <Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useInterval } from 'react-use'; import { useInterval, useMount } from 'react-use';
import styled from 'styled-components'; import styled from 'styled-components';
import { Data } from '../../../../data/data'; import { Data } from '../../../../data/data';
import { getConversationController } from '../../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
@ -61,9 +61,9 @@ function useIsExpired(
checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment); checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment);
} }
useEffect(() => { useMount(() => {
void checkExpired(); void checkExpired();
}, [checkExpired]); // check on mount }); // check on mount
useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed

@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { contextMenu } from 'react-contexify'; import { contextMenu } from 'react-contexify';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import styled, { keyframes } from 'styled-components'; import styled, { keyframes } from 'styled-components';
import { isNil, isString, toNumber } from 'lodash';
import { MessageRenderingProps } from '../../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { import {
@ -99,13 +100,27 @@ export const GenericReadableMessage = (props: Props) => {
const handleContextMenu = useCallback( const handleContextMenu = useCallback(
(e: React.MouseEvent<HTMLElement>) => { (e: React.MouseEvent<HTMLElement>) => {
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
// Note: If you change this, also make sure to update the `saveAttachment()` in MessageContextMenu.tsx
const enableContextMenu = !multiSelectMode && !msgProps?.isKickedFromGroup; const enableContextMenu = !multiSelectMode && !msgProps?.isKickedFromGroup;
const attachmentIndexStr = (e?.target as any)?.parentElement?.getAttribute?.(
'data-attachmentindex'
);
const attachmentIndex =
isString(attachmentIndexStr) && !isNil(toNumber(attachmentIndexStr))
? toNumber(attachmentIndexStr)
: 0;
if (enableContextMenu) { if (enableContextMenu) {
contextMenu.hideAll(); contextMenu.hideAll();
contextMenu.show({ contextMenu.show({
id: ctxMenuID, id: ctxMenuID,
event: e, event: e,
props: {
dataAttachmentIndex: attachmentIndex,
},
}); });
} }
setIsRightClicked(enableContextMenu); setIsRightClicked(enableContextMenu);

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { animation, Menu } from 'react-contexify'; import { Menu } from 'react-contexify';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { isSearching } from '../../state/selectors/search'; import { isSearching } from '../../state/selectors/search';
import { import {
@ -58,7 +58,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
return ( return (
<ContextConversationProvider value={convoId}> <ContextConversationProvider value={convoId}>
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
<NotificationForConvoMenuItem /> <NotificationForConvoMenuItem />
<BlockMenuItem /> <BlockMenuItem />
<CopyMenuItem /> <CopyMenuItem />

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { animation, Item, Menu } from 'react-contexify'; import { Item, Menu } from 'react-contexify';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector'; import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector';
@ -44,7 +44,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
{/* Message request related actions */} {/* Message request related actions */}
<AcceptMsgRequestMenuItem /> <AcceptMsgRequestMenuItem />
<DeclineMsgRequestMenuItem /> <DeclineMsgRequestMenuItem />

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { animation, Item, Menu } from 'react-contexify'; import { Item, Menu } from 'react-contexify';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer'; import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
@ -28,7 +28,7 @@ export const MessageRequestBannerContextMenu = (props: PropsContextConversationI
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation="fade">
<HideBannerMenuItem /> <HideBannerMenuItem />
</Menu> </Menu>
</SessionContextMenuContainer> </SessionContextMenuContainer>

@ -546,7 +546,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// an OpenGroupV2 message is just a visible message // an OpenGroupV2 message is just a visible message
const chatMessageParams: VisibleMessageParams = { const chatMessageParams: VisibleMessageParams = {
body: '', body: '',
timestamp: sentAt, // we need to use a new timestamp here, otherwise android&iOS will consider this message as a duplicate and drop the synced reaction
timestamp: GetNetworkTime.getNowWithNetworkOffset(),
reaction, reaction,
lokiProfile: UserUtils.getOurProfile(), lokiProfile: UserUtils.getOurProfile(),
}; };

@ -35,6 +35,7 @@ import { getAllCachedECKeyPair, sentAtMoreRecentThanWrapper } from './closedGrou
import { ConfigMessageHandler } from './configMessage'; import { ConfigMessageHandler } from './configMessage';
import { ECKeyPair } from './keypairs'; import { ECKeyPair } from './keypairs';
import { ContactsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface'; import { ContactsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface';
import { isUsFromCache } from '../session/utils/User';
import { import {
changeToDisappearingMessageType, changeToDisappearingMessageType,
checkForExpireUpdateInContentMessage, checkForExpireUpdateInContentMessage,
@ -298,27 +299,42 @@ async function decrypt(envelope: EnvelopePlus): Promise<any> {
return plaintext; return plaintext;
} }
async function shouldDropIncomingPrivateMessage(sentAtTimestamp: number, envelope: EnvelopePlus) { async function shouldDropIncomingPrivateMessage(
sentAtTimestamp: number,
envelope: EnvelopePlus,
content: SignalService.Content
) {
// sentAtMoreRecentThanWrapper is going to be true, if the latest contact wrapper we processed was roughly more recent that this message timestamp // sentAtMoreRecentThanWrapper is going to be true, if the latest contact wrapper we processed was roughly more recent that this message timestamp
const moreRecentOrNah = await sentAtMoreRecentThanWrapper(sentAtTimestamp, 'ContactsConfig'); const moreRecentOrNah = await sentAtMoreRecentThanWrapper(sentAtTimestamp, 'ContactsConfig');
const isSyncedMessage = isUsFromCache(envelope.source);
if (moreRecentOrNah === 'wrapper_more_recent') { if (moreRecentOrNah === 'wrapper_more_recent') {
// we need to check if that conversation is already in the wrapper, and if yes // we need to check if that conversation is already in the wrapper
try { try {
const privateConvoInWrapper = await ContactsWrapperActions.get(envelope.source); // let's check if the corresponding conversation is hidden in the contacts wrapper or not.
// the corresponding conversation is syncTarget when this is a synced message only, so we need to rely on it first, then the envelope.source.
const syncTargetOrSource = isSyncedMessage
? content.dataMessage?.syncTarget || undefined
: envelope.source;
if (!syncTargetOrSource) {
return false;
}
const privateConvoInWrapper = await ContactsWrapperActions.get(syncTargetOrSource);
if ( if (
!privateConvoInWrapper || !privateConvoInWrapper ||
privateConvoInWrapper.priority <= CONVERSATION_PRIORITIES.hidden privateConvoInWrapper.priority <= CONVERSATION_PRIORITIES.hidden
) { ) {
// the wrapper is more recent that this message and there is no such private conversation. Just drop this incoming message. // the wrapper is more recent that this message and there is no such private conversation. Just drop this incoming message.
window.log.info( window.log.info(
`received message from contact ${envelope.source} which appears to be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. Dropping it` `received message on conversation ${syncTargetOrSource} which appears to be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. Dropping it`
); );
return true; return true;
} }
window.log.info( window.log.info(
`received message from contact ${envelope.source} which appears to NOT be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. ` `received message on conversation ${syncTargetOrSource} which appears to NOT be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. `
); );
} catch (e) { } catch (e) {
window.log.warn( window.log.warn(
@ -415,7 +431,7 @@ export async function innerHandleSwarmContentMessage(
const isPrivateConversationMessage = !envelope.senderIdentity; const isPrivateConversationMessage = !envelope.senderIdentity;
if (isPrivateConversationMessage) { if (isPrivateConversationMessage) {
if (await shouldDropIncomingPrivateMessage(sentAtTimestamp, envelope)) { if (await shouldDropIncomingPrivateMessage(sentAtTimestamp, envelope, content)) {
await removeFromCache(envelope); await removeFromCache(envelope);
return; return;
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save