Requesting flow working with sending message as acceptance.
This commit is contained in:
parent
cdeac8f424
commit
d627b8e11d
|
@ -435,7 +435,7 @@
|
|||
"notificationSubtitle": "Notifications - $setting$",
|
||||
"surveyTitle": "Take our Session Survey",
|
||||
"goToOurSurvey": "Go to our survey",
|
||||
"blockAll": "Block All",
|
||||
"clearAll": "Clear All",
|
||||
"messageRequests": "Message Requests",
|
||||
"requestsSubtitle": "Pending Requests",
|
||||
"requestsPlaceholder": "No requests",
|
||||
|
@ -467,5 +467,9 @@
|
|||
"trimDatabaseDescription": "Reduces your message database size to your last 10,000 messages.",
|
||||
"trimDatabaseConfirmationBody": "Are you sure you want to delete your $deleteAmount$ oldest received messages?",
|
||||
"reportAsSpam": "REPORT AS SPAM",
|
||||
"messageRequestPending": "Your message request is currently pending",
|
||||
"messageRequestAccepted": "Your message request has been accepted",
|
||||
"messageRequestAcceptedOurs": "You have accepted $name$'s message request",
|
||||
"declineRequestMessage": "Are you sure you want to decline this message request?",
|
||||
"respondingToRequestWarning": "Sending a message to this user will automatically accept their message request and reveal your Session ID."
|
||||
}
|
||||
|
|
50
app/sql.js
50
app/sql.js
|
@ -840,6 +840,7 @@ const LOKI_SCHEMA_VERSIONS = [
|
|||
updateToLokiSchemaVersion17,
|
||||
updateToLokiSchemaVersion18,
|
||||
updateToLokiSchemaVersion19,
|
||||
updateToLokiSchemaVersion20,
|
||||
];
|
||||
|
||||
function updateToLokiSchemaVersion1(currentVersion, db) {
|
||||
|
@ -1335,6 +1336,55 @@ function updateToLokiSchemaVersion19(currentVersion, db) {
|
|||
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
|
||||
}
|
||||
|
||||
function updateToLokiSchemaVersion20(currentVersion, db) {
|
||||
const targetVersion = 20;
|
||||
// if (currentVersion >= targetVersion) {
|
||||
// return;
|
||||
// }
|
||||
console.log(`updateToLokiSchemaVersion${targetVersion}: starting...`);
|
||||
db.transaction(() => {
|
||||
db.exec(`
|
||||
UPDATE ${CONVERSATIONS_TABLE} SET
|
||||
json = json_set(json, '$.didApproveMe', 1, '$.isApproved', 1)
|
||||
WHERE type = 'private';
|
||||
`);
|
||||
|
||||
// all closed group admins
|
||||
const closedGroupRows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT json FROM ${CONVERSATIONS_TABLE} WHERE
|
||||
type = 'group' AND
|
||||
id NOT LIKE 'publicChat:%';
|
||||
`
|
||||
)
|
||||
.all();
|
||||
|
||||
console.warn({ closedGroupRows });
|
||||
|
||||
const adminIds = closedGroupRows.map(json => {
|
||||
return jsonToObject(json).groupAdmins;
|
||||
});
|
||||
console.warn({ adminIds });
|
||||
forEach(adminIds, id => {
|
||||
db.exec(
|
||||
`
|
||||
UPDATE ${CONVERSATIONS_TABLE} SET
|
||||
json = json_set(json, '$.didApproveMe', 1, '$.isApproved', 1)
|
||||
WHERE type = id
|
||||
values ($id);
|
||||
`
|
||||
).run({
|
||||
id,
|
||||
});
|
||||
});
|
||||
|
||||
writeLokiSchemaVersion(targetVersion, db);
|
||||
})();
|
||||
|
||||
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
|
||||
}
|
||||
|
||||
function writeLokiSchemaVersion(newVersion, db) {
|
||||
db.prepare(
|
||||
`INSERT INTO loki_schema(
|
||||
|
|
|
@ -37,7 +37,7 @@ window.isBehindProxy = () => Boolean(config.proxyUrl);
|
|||
|
||||
window.lokiFeatureFlags = {
|
||||
useOnionRequests: true,
|
||||
useMessageRequests: false,
|
||||
useMessageRequests: true,
|
||||
useCallMessage: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -47,11 +47,13 @@ import {
|
|||
} from '../../types/attachments/VisualAttachment';
|
||||
import { blobToArrayBuffer } from 'blob-util';
|
||||
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getOverlayMode } from '../../state/selectors/section';
|
||||
import styled from 'styled-components';
|
||||
import { Flex } from '../basic/Flex';
|
||||
import { blockConvoById } from '../../interactions/conversationInteractions';
|
||||
import {
|
||||
acceptConversation,
|
||||
blockConvoById,
|
||||
declineConversation,
|
||||
} from '../../interactions/conversationInteractions';
|
||||
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
|
||||
// tslint:disable: jsx-curly-spacing
|
||||
|
||||
interface State {
|
||||
|
@ -226,13 +228,39 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
return <MessageView />;
|
||||
}
|
||||
|
||||
// // TODO: refactor component and use overlay hook
|
||||
// const overlayMode = useSelector(getOverlayMode);
|
||||
// // either use overlayMode == messageRequest or use Conversation.isAPproved === false;
|
||||
const conversation = getConversationController().get(selectedConversation.id);
|
||||
const isApproved = conversation.isApproved();
|
||||
|
||||
const selectionMode = selectedMessages.length > 0;
|
||||
const useMsgRequests =
|
||||
window.lokiFeatureFlags.useMessageRequests &&
|
||||
window.inboxStore?.getState().userConfig.messageRequests;
|
||||
const showMsgRequestUI = useMsgRequests && !isApproved && messagesProps.length > 0;
|
||||
|
||||
const handleDeclineConversationRequest = async () => {
|
||||
window.inboxStore?.dispatch(
|
||||
updateConfirmModal({
|
||||
okText: window.i18n('decline'),
|
||||
cancelText: window.i18n('cancel'),
|
||||
message: window.i18n('declineRequestMessage'),
|
||||
onClickOk: async () => {
|
||||
declineConversation(selectedConversation.id, false);
|
||||
blockConvoById(selectedConversation.id);
|
||||
forceSyncConfigurationNowIfNeeded();
|
||||
},
|
||||
onClickCancel: () => {
|
||||
window.inboxStore?.dispatch(updateConfirmModal(null));
|
||||
},
|
||||
onClickClose: () => {
|
||||
window.inboxStore?.dispatch(updateConfirmModal(null));
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleAcceptConversationRequest = async () => {
|
||||
const { id } = selectedConversation;
|
||||
await acceptConversation(id, true);
|
||||
};
|
||||
|
||||
return (
|
||||
<SessionTheme>
|
||||
|
@ -252,34 +280,29 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
|
||||
|
||||
<div className="conversation-messages">
|
||||
{!isApproved && (
|
||||
{showMsgRequestUI && (
|
||||
<ConversationRequestBanner>
|
||||
<div className="conversation-request-banner__row">
|
||||
<SessionButton
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
buttonType={SessionButtonType.BrandOutline}
|
||||
onClick={() => {
|
||||
getConversationController()
|
||||
.get(selectedConversation.id)
|
||||
.setIsApproved(true);
|
||||
}}
|
||||
onClick={handleAcceptConversationRequest}
|
||||
text={window.i18n('accept')}
|
||||
/>
|
||||
<SessionButton
|
||||
buttonColor={SessionButtonColor.Danger}
|
||||
buttonType={SessionButtonType.BrandOutline}
|
||||
text={window.i18n('decline')}
|
||||
onClick={async () => {
|
||||
getConversationController();
|
||||
blockConvoById(selectedConversation.id);
|
||||
}}
|
||||
onClick={handleDeclineConversationRequest}
|
||||
/>
|
||||
</div>
|
||||
{/*
|
||||
Disabling for now until report as spam is added in
|
||||
<SessionButton
|
||||
buttonColor={SessionButtonColor.Danger}
|
||||
buttonType={SessionButtonType.Simple}
|
||||
text={window.i18n('reportAsSpam')}
|
||||
/>
|
||||
/> */}
|
||||
</ConversationRequestBanner>
|
||||
)}
|
||||
<SplitViewContainer
|
||||
|
@ -293,7 +316,7 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
{isDraggingFile && <SessionFileDropzone />}
|
||||
</div>
|
||||
|
||||
{!isApproved && (
|
||||
{showMsgRequestUI && (
|
||||
<ConversationRequestTextBottom>
|
||||
<ConversationRequestTextInner>
|
||||
{window.i18n('respondingToRequestWarning')}
|
||||
|
|
|
@ -2,7 +2,11 @@ import React from 'react';
|
|||
import { useSelector } from 'react-redux';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import useKey from 'react-use/lib/useKey';
|
||||
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../models/messageType';
|
||||
import {
|
||||
PropsForDataExtractionNotification,
|
||||
PropsForMessageRequestResponse,
|
||||
QuoteClickOptions,
|
||||
} from '../../models/messageType';
|
||||
import {
|
||||
PropsForCallNotification,
|
||||
PropsForExpirationTimer,
|
||||
|
@ -11,7 +15,7 @@ import {
|
|||
} from '../../state/ducks/conversations';
|
||||
import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations';
|
||||
import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage';
|
||||
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
|
||||
import { MessageRequestResponse } from './message/message-item/MessageRequestResponse';
|
||||
import { MessageDateBreak } from './message/message-item/DateBreak';
|
||||
import { GroupInvitation } from './message/message-item/GroupInvitation';
|
||||
import { Message } from './message/message-item/Message';
|
||||
|
@ -19,6 +23,7 @@ import { CallNotification } from './message/message-item/notification-bubble/Cal
|
|||
|
||||
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
|
||||
import { TimerNotification } from './TimerNotification';
|
||||
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
|
||||
|
||||
function isNotTextboxEvent(e: KeyboardEvent) {
|
||||
return (e?.target as any)?.type === undefined;
|
||||
|
@ -79,6 +84,16 @@ export const SessionMessagesList = (props: {
|
|||
return [<GroupInvitation key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
|
||||
}
|
||||
|
||||
if (messageProps.message?.messageType === 'message-request-response') {
|
||||
const msgProps = messageProps.message.props as PropsForMessageRequestResponse;
|
||||
|
||||
return [
|
||||
<MessageRequestResponse key={messageId} {...msgProps} />,
|
||||
dateBreak,
|
||||
unreadIndicator,
|
||||
];
|
||||
}
|
||||
|
||||
if (messageProps.message?.messageType === 'data-extraction') {
|
||||
const msgProps = messageProps.message.props as PropsForDataExtractionNotification;
|
||||
|
||||
|
|
|
@ -424,16 +424,27 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { isKickedFromGroup, left, isPrivate, isBlocked } = this.props.selectedConversation;
|
||||
const messagePlaceHolder = isKickedFromGroup
|
||||
? i18n('youGotKickedFromGroup')
|
||||
: left
|
||||
? i18n('youLeftTheGroup')
|
||||
: isBlocked && isPrivate
|
||||
? i18n('unblockToSend')
|
||||
: isBlocked && !isPrivate
|
||||
? i18n('unblockGroupToSend')
|
||||
: i18n('sendMessage');
|
||||
const {
|
||||
isKickedFromGroup,
|
||||
left,
|
||||
isPrivate,
|
||||
isBlocked,
|
||||
didApproveMe,
|
||||
isApproved,
|
||||
} = this.props.selectedConversation;
|
||||
const messagePlaceHolder =
|
||||
// isApproved && !didApproveMe && isPrivate
|
||||
isApproved && !didApproveMe && isPrivate
|
||||
? i18n('messageRequestPending')
|
||||
: isKickedFromGroup
|
||||
? i18n('youGotKickedFromGroup')
|
||||
: left
|
||||
? i18n('youLeftTheGroup')
|
||||
: isBlocked && isPrivate
|
||||
? i18n('unblockToSend')
|
||||
: isBlocked && !isPrivate
|
||||
? i18n('unblockGroupToSend')
|
||||
: i18n('sendMessage');
|
||||
const { typingEnabled } = this.props;
|
||||
|
||||
return (
|
||||
|
@ -785,6 +796,14 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
ToastUtils.pushUnblockToSend();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
selectedConversation.isApproved &&
|
||||
!selectedConversation.didApproveMe &&
|
||||
selectedConversation.isPrivate
|
||||
) {
|
||||
ToastUtils.pushMessageRequestPending();
|
||||
return;
|
||||
}
|
||||
if (selectedConversation.isBlocked && !selectedConversation.isPrivate) {
|
||||
ToastUtils.pushUnblockToSendGroup();
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import { PropsForMessageRequestResponse } from '../../../../models/messageType';
|
||||
import { getConversationController } from '../../../../session/conversations';
|
||||
import { UserUtils } from '../../../../session/utils';
|
||||
import { Flex } from '../../../basic/Flex';
|
||||
import { SpacerSM, Text } from '../../../basic/Text';
|
||||
import { ReadableMessage } from './ReadableMessage';
|
||||
|
||||
export const MessageRequestResponse = (props: PropsForMessageRequestResponse) => {
|
||||
const { messageId, isUnread, receivedAt, conversationId, source } = props;
|
||||
|
||||
let profileName = '';
|
||||
if (conversationId) {
|
||||
profileName =
|
||||
getConversationController()
|
||||
.get(conversationId)
|
||||
.getProfileName() + '';
|
||||
}
|
||||
const msgText =
|
||||
profileName && props.source === UserUtils.getOurPubKeyStrFromCache()
|
||||
? window.i18n('messageRequestAcceptedOurs', [profileName])
|
||||
: window.i18n('messageRequestAccepted');
|
||||
|
||||
return (
|
||||
<ReadableMessage
|
||||
messageId={messageId}
|
||||
receivedAt={receivedAt}
|
||||
isUnread={isUnread}
|
||||
key={`readable-message-${messageId}`}
|
||||
>
|
||||
<Flex
|
||||
container={true}
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
margin={'var(--margins-sm)'}
|
||||
id={`msg-${messageId}`}
|
||||
>
|
||||
<SpacerSM />
|
||||
<Text text={msgText} subtle={true} ellipsisOverflow={true} />
|
||||
</Flex>
|
||||
</ReadableMessage>
|
||||
);
|
||||
};
|
|
@ -28,6 +28,8 @@ export const LeftPaneSectionHeader = (props: { buttonClicked?: any }) => {
|
|||
const isMessageSection = focusedSection === SectionType.Message;
|
||||
const isMessageRequestOverlay = overlayMode === 'message-requests';
|
||||
|
||||
const showBackButton = isMessageRequestOverlay && isMessageSection;
|
||||
|
||||
switch (focusedSection) {
|
||||
case SectionType.Contact:
|
||||
label = window.i18n('contactsHeader');
|
||||
|
@ -46,7 +48,7 @@ export const LeftPaneSectionHeader = (props: { buttonClicked?: any }) => {
|
|||
return (
|
||||
<Flex flexDirection="column">
|
||||
<div className="module-left-pane__header">
|
||||
{isMessageRequestOverlay && (
|
||||
{showBackButton && (
|
||||
<SessionIconButton
|
||||
onClick={() => {
|
||||
dispatch(setOverlayMode(undefined));
|
||||
|
|
|
@ -6,7 +6,6 @@ import { MessageBody } from '../../conversation/message/message-content/MessageB
|
|||
import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus';
|
||||
import { TypingAnimation } from '../../conversation/TypingAnimation';
|
||||
import { ContextConversationId } from './ConversationListItem';
|
||||
import { MessageRequestButtons } from './MessageRequest';
|
||||
|
||||
function useMessageItemProps(convoId: string) {
|
||||
const convoProps = useConversationPropsById(convoId);
|
||||
|
@ -51,7 +50,6 @@ export const MessageItem = (props: { isMessageRequest: boolean }) => {
|
|||
<MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
|
||||
)}
|
||||
</div>
|
||||
<MessageRequestButtons isMessageRequest={props.isMessageRequest} />
|
||||
{lastMessage && lastMessage.status && !props.isMessageRequest ? (
|
||||
<OutgoingMessageStatus status={lastMessage.status} />
|
||||
) : null}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import React, { useContext } from 'react';
|
||||
import {
|
||||
approveConversation,
|
||||
blockConvoById,
|
||||
} from '../../../interactions/conversationInteractions';
|
||||
import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/syncUtils';
|
||||
import { SessionIconButton } from '../../icon';
|
||||
import { ContextConversationId } from './ConversationListItem';
|
||||
|
||||
const RejectMessageRequestButton = () => {
|
||||
const conversationId = useContext(ContextConversationId);
|
||||
|
||||
/**
|
||||
* Removes conversation from requests list,
|
||||
* adds ID to block list, syncs the block with linked devices.
|
||||
*/
|
||||
const handleConversationBlock = async () => {
|
||||
await blockConvoById(conversationId);
|
||||
await forceSyncConfigurationNowIfNeeded();
|
||||
};
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconType="exit"
|
||||
iconSize="large"
|
||||
onClick={handleConversationBlock}
|
||||
backgroundColor="var(--color-destructive)"
|
||||
iconColor="var(--color-foreground-primary)"
|
||||
iconPadding="var(--margins-xs)"
|
||||
borderRadius="2px"
|
||||
margin="0 5px 0 0"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ApproveMessageRequestButton = () => {
|
||||
const conversationId = useContext(ContextConversationId);
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconType="check"
|
||||
iconSize="large"
|
||||
onClick={async () => {
|
||||
await approveConversation(conversationId);
|
||||
}}
|
||||
backgroundColor="var(--color-accent)"
|
||||
iconColor="var(--color-foreground-primary)"
|
||||
iconPadding="var(--margins-xs)"
|
||||
borderRadius="2px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageRequestButtons = ({ isMessageRequest }: { isMessageRequest: boolean }) => {
|
||||
if (!isMessageRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RejectMessageRequestButton />
|
||||
<ApproveMessageRequestButton />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -35,20 +35,21 @@ async function handleBlockAllRequestsClick(messageRequestSetting: boolean) {
|
|||
return;
|
||||
}
|
||||
|
||||
const conversationRequests = conversations.filter(
|
||||
const convoRequestsToBlock = conversations.filter(
|
||||
c => c.isPrivate() && c.get('active_at') && c.get('isApproved')
|
||||
);
|
||||
|
||||
let syncRequired = false;
|
||||
|
||||
if (!conversationRequests) {
|
||||
if (!convoRequestsToBlock) {
|
||||
window?.log?.info('No conversation requests to block.');
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
conversationRequests.map(async convo => {
|
||||
convoRequestsToBlock.map(async convo => {
|
||||
await BlockedNumberController.block(convo.id);
|
||||
await convo.setIsApproved(false);
|
||||
syncRequired = true;
|
||||
})
|
||||
);
|
||||
|
@ -67,7 +68,7 @@ export const OverlayMessageRequest = () => {
|
|||
|
||||
const messageRequestSetting = useSelector(getIsMessageRequestsEnabled);
|
||||
|
||||
const buttonText = window.i18n('blockAll');
|
||||
const buttonText = window.i18n('clearAll');
|
||||
|
||||
return (
|
||||
<div className="module-left-pane-overlay">
|
||||
|
|
|
@ -121,9 +121,9 @@ export async function unblockConvoById(conversationId: string) {
|
|||
}
|
||||
|
||||
/**
|
||||
* marks the conversation as approved.
|
||||
* marks the conversation's approval fields, sends messageRequestResponse, syncs to linked devices
|
||||
*/
|
||||
export const approveConversation = async (conversationId: string) => {
|
||||
export const acceptConversation = async (conversationId: string, syncToDevices: boolean = true) => {
|
||||
const conversationToApprove = getConversationController().get(conversationId);
|
||||
|
||||
if (!conversationToApprove || conversationToApprove.isApproved()) {
|
||||
|
@ -131,10 +131,38 @@ export const approveConversation = async (conversationId: string) => {
|
|||
return;
|
||||
}
|
||||
|
||||
await conversationToApprove.setIsApproved(true);
|
||||
Promise.all([
|
||||
await conversationToApprove.setIsApproved(true),
|
||||
await conversationToApprove.setDidApproveMe(true),
|
||||
]);
|
||||
await conversationToApprove.sendMessageRequestResponse(true);
|
||||
|
||||
// Conversation was not approved before so a sync is needed
|
||||
await forceSyncConfigurationNowIfNeeded();
|
||||
if (syncToDevices) {
|
||||
await forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the approval fields to false for conversation. Sends decline message.
|
||||
*/
|
||||
export const declineConversation = async (
|
||||
conversationId: string,
|
||||
syncToDevices: boolean = true
|
||||
) => {
|
||||
const conversationToDecline = getConversationController().get(conversationId);
|
||||
|
||||
if (!conversationToDecline || conversationToDecline.isApproved()) {
|
||||
window?.log?.info('Conversation is already declined.');
|
||||
return;
|
||||
}
|
||||
|
||||
await conversationToDecline.setIsApproved(false);
|
||||
|
||||
// Conversation was not approved before so a sync is needed
|
||||
if (syncToDevices) {
|
||||
await forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
};
|
||||
|
||||
export async function showUpdateGroupNameByConvoId(conversationId: string) {
|
||||
|
|
|
@ -4,12 +4,12 @@ import { getMessageQueue } from '../session';
|
|||
import { getConversationController } from '../session/conversations';
|
||||
import { ClosedGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
|
||||
import { PubKey } from '../session/types';
|
||||
import { UserUtils } from '../session/utils';
|
||||
import { ToastUtils, UserUtils } from '../session/utils';
|
||||
import { BlockedNumberController } from '../util';
|
||||
import { leaveClosedGroup } from '../session/group/closed-group';
|
||||
import { SignalService } from '../protobuf';
|
||||
import { MessageModel } from './message';
|
||||
import { MessageAttributesOptionals, MessageModelType } from './messageType';
|
||||
import { MessageAttributesOptionals, MessageDirection, MessageModelType } from './messageType';
|
||||
import autoBind from 'auto-bind';
|
||||
import {
|
||||
getMessagesByConversation,
|
||||
|
@ -19,7 +19,7 @@ import {
|
|||
saveMessages,
|
||||
updateConversation,
|
||||
} from '../../ts/data/data';
|
||||
import { toHex } from '../session/utils/String';
|
||||
import { fromHexToArray, toHex } from '../session/utils/String';
|
||||
import {
|
||||
actions as conversationActions,
|
||||
conversationChanged,
|
||||
|
@ -59,6 +59,8 @@ import {
|
|||
getAbsoluteAttachmentPath,
|
||||
loadAttachmentData,
|
||||
} from '../types/MessageAttachment';
|
||||
import { getOurPubKeyStrFromCache } from '../session/utils/User';
|
||||
import { MessageRequestResponse } from '../session/messages/outgoing/controlMessage/MessageRequestResponse';
|
||||
|
||||
export enum ConversationTypeEnum {
|
||||
GROUP = 'group',
|
||||
|
@ -112,6 +114,7 @@ export interface ConversationAttributes {
|
|||
isTrustedForAttachmentDownload: boolean;
|
||||
isPinned: boolean;
|
||||
isApproved: boolean;
|
||||
didApproveMe: boolean;
|
||||
}
|
||||
|
||||
export interface ConversationAttributesOptionals {
|
||||
|
@ -151,6 +154,7 @@ export interface ConversationAttributesOptionals {
|
|||
isTrustedForAttachmentDownload?: boolean;
|
||||
isPinned: boolean;
|
||||
isApproved?: boolean;
|
||||
didApproveMe?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,6 +184,7 @@ export const fillConvoAttributesWithDefaults = (
|
|||
isTrustedForAttachmentDownload: false, // we don't trust a contact until we say so
|
||||
isPinned: false,
|
||||
isApproved: false,
|
||||
didApproveMe: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -341,6 +346,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
const subscriberCount = this.get('subscriberCount');
|
||||
const isPinned = this.isPinned();
|
||||
const isApproved = this.isApproved();
|
||||
const didApproveMe = this.didApproveMe();
|
||||
const hasNickname = !!this.getNickname();
|
||||
const isKickedFromGroup = !!this.get('isKickedFromGroup');
|
||||
const left = !!this.get('left');
|
||||
|
@ -416,6 +422,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
if (isPinned) {
|
||||
toRet.isPinned = isPinned;
|
||||
}
|
||||
if (isApproved) {
|
||||
toRet.didApproveMe = didApproveMe;
|
||||
}
|
||||
if (isApproved) {
|
||||
toRet.isApproved = isApproved;
|
||||
}
|
||||
|
@ -634,11 +643,28 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
lokiProfile: UserUtils.getOurProfile(),
|
||||
};
|
||||
|
||||
const updateApprovalNeeded =
|
||||
!this.isApproved() && (this.isPrivate() || this.isMediumGroup() || this.isClosedGroup());
|
||||
if (updateApprovalNeeded) {
|
||||
const shouldApprove = !this.isApproved() && this.isPrivate();
|
||||
const hasMsgsFromOther =
|
||||
(await getMessagesByConversation(this.id, { type: MessageDirection.incoming })).length > 0;
|
||||
console.warn(hasMsgsFromOther);
|
||||
if (shouldApprove) {
|
||||
await this.setIsApproved(true);
|
||||
void forceSyncConfigurationNowIfNeeded();
|
||||
if (!this.didApproveMe() && hasMsgsFromOther) {
|
||||
console.warn('This is a reply message sending message request acceptance response.');
|
||||
// TODO: if this is a reply, send messageRequestAccept
|
||||
|
||||
await this.setDidApproveMe(true);
|
||||
await this.sendMessageRequestResponse(true);
|
||||
void forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
// void forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
|
||||
// TODO: remove once dev-tested
|
||||
if (chatMessageParams.body?.includes('unapprove')) {
|
||||
await this.setIsApproved(false);
|
||||
await this.setDidApproveMe(false);
|
||||
// void forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
|
||||
if (this.isOpenGroupV2()) {
|
||||
|
@ -705,6 +731,37 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
}
|
||||
}
|
||||
|
||||
public async sendMessageRequestResponse(isApproved: boolean) {
|
||||
const publicKey = fromHexToArray(getOurPubKeyStrFromCache());
|
||||
// const msgRequestResponseParams:
|
||||
const timestamp = Date.now();
|
||||
|
||||
const messageRequestResponseParams = {
|
||||
timestamp,
|
||||
publicKey,
|
||||
isApproved,
|
||||
};
|
||||
|
||||
const messageRequestResponse = new MessageRequestResponse(messageRequestResponseParams);
|
||||
|
||||
if (this.isPrivate()) {
|
||||
// 1-1 conversations
|
||||
const pubkeyForSending = new PubKey(this.id);
|
||||
await getMessageQueue()
|
||||
.sendToPubKey(pubkeyForSending, messageRequestResponse)
|
||||
.catch(window?.log?.error);
|
||||
}
|
||||
// TODO: may be removable as group invites are implied to be friends.
|
||||
// else if (this.isClosedGroup()) {
|
||||
// // group conversations
|
||||
// await getMessageQueue()
|
||||
// .sendToGroup(messageRequestResponse, undefined, new PubKey(this.id))
|
||||
// .catch(window?.log?.error);
|
||||
// }
|
||||
|
||||
console.warn('Sent message request response', isApproved);
|
||||
}
|
||||
|
||||
public async sendMessage(msg: SendMessageType) {
|
||||
const { attachments, body, groupInvitation, preview, quote } = msg;
|
||||
this.clearTypingTimers();
|
||||
|
@ -923,16 +980,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
public async addSingleMessage(messageAttributes: MessageAttributesOptionals, setToExpire = true) {
|
||||
const model = new MessageModel(messageAttributes);
|
||||
|
||||
const isMe = messageAttributes.source === UserUtils.getOurPubKeyStrFromCache();
|
||||
|
||||
if (
|
||||
isMe &&
|
||||
window.lokiFeatureFlags.useMessageRequests &&
|
||||
window.inboxStore?.getState().userConfig.messageRequests
|
||||
) {
|
||||
await this.setIsApproved(true);
|
||||
}
|
||||
|
||||
// no need to trigger a UI update now, we trigger a messageAdded just below
|
||||
const messageId = await model.commit(false);
|
||||
model.set({ id: messageId });
|
||||
|
@ -1180,6 +1227,23 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
isApproved: value,
|
||||
});
|
||||
|
||||
if (!this.isApproved() && value) {
|
||||
// if it's false or hasnt been set, send approval msg
|
||||
this.sendMessageRequestResponse(true);
|
||||
void forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
|
||||
await this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public async setDidApproveMe(value: boolean) {
|
||||
if (value !== this.didApproveMe()) {
|
||||
window?.log?.info(`Setting ${this.attributes.profileName} didApproveMe to:: ${value}`);
|
||||
this.set({
|
||||
didApproveMe: value,
|
||||
});
|
||||
|
||||
await this.commit();
|
||||
}
|
||||
}
|
||||
|
@ -1271,6 +1335,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
return Boolean(this.get('isPinned'));
|
||||
}
|
||||
|
||||
public didApproveMe() {
|
||||
return Boolean(this.get('didApproveMe'));
|
||||
}
|
||||
|
||||
public isApproved() {
|
||||
return Boolean(this.get('isApproved'));
|
||||
}
|
||||
|
@ -1379,6 +1447,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
}
|
||||
const conversationId = this.id;
|
||||
|
||||
if (!this.isApproved()) {
|
||||
window?.log?.info('notification cancelled for unapproved convo', this.idForLogging());
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the notifications are not muted for this convo (and not the source convo)
|
||||
const convNotif = this.get('triggerNotificationsFor');
|
||||
if (convNotif === 'disabled') {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
MessageGroupUpdate,
|
||||
MessageModelType,
|
||||
PropsForDataExtractionNotification,
|
||||
PropsForMessageRequestResponse,
|
||||
} from './messageType';
|
||||
|
||||
import autoBind from 'auto-bind';
|
||||
|
@ -106,6 +107,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
|
|||
const propsForGroupInvitation = this.getPropsForGroupInvitation();
|
||||
const propsForGroupUpdateMessage = this.getPropsForGroupUpdateMessage();
|
||||
const propsForTimerNotification = this.getPropsForTimerNotification();
|
||||
const propsForMessageRequestResponse = this.getPropsForMessageRequestResponse();
|
||||
const callNotificationType = this.get('callNotificationType');
|
||||
const messageProps: MessageModelPropsWithoutConvoProps = {
|
||||
propsForMessage: this.getPropsForMessage(),
|
||||
|
@ -113,6 +115,9 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
|
|||
if (propsForDataExtractionNotification) {
|
||||
messageProps.propsForDataExtractionNotification = propsForDataExtractionNotification;
|
||||
}
|
||||
if (propsForMessageRequestResponse) {
|
||||
messageProps.propsForMessageRequestResponse = propsForMessageRequestResponse;
|
||||
}
|
||||
if (propsForGroupInvitation) {
|
||||
messageProps.propsForGroupInvitation = propsForGroupInvitation;
|
||||
}
|
||||
|
@ -176,6 +181,10 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
|
|||
return !!this.get('groupInvitation');
|
||||
}
|
||||
|
||||
public isMessageRequestResponse() {
|
||||
return !!this.get('messageRequestResponse');
|
||||
}
|
||||
|
||||
public isDataExtractionNotification() {
|
||||
return !!this.get('dataExtractionNotification');
|
||||
}
|
||||
|
@ -300,6 +309,30 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
|
|||
};
|
||||
}
|
||||
|
||||
public getPropsForMessageRequestResponse(): PropsForMessageRequestResponse | null {
|
||||
if (!this.isMessageRequestResponse()) {
|
||||
return null;
|
||||
}
|
||||
const messageRequestResponse = this.get('messageRequestResponse');
|
||||
|
||||
if (!messageRequestResponse) {
|
||||
window.log.warn('messageRequestResponse should not happen');
|
||||
return null;
|
||||
}
|
||||
|
||||
const contact = this.findAndFormatContact(messageRequestResponse.source);
|
||||
|
||||
return {
|
||||
...messageRequestResponse,
|
||||
name: contact.profileName || contact.name || messageRequestResponse.source,
|
||||
messageId: this.id,
|
||||
receivedAt: this.get('received_at'),
|
||||
isUnread: this.isUnread(),
|
||||
conversationId: this.get('conversationId'),
|
||||
source: this.get('source'),
|
||||
};
|
||||
}
|
||||
|
||||
public findContact(pubkey: string) {
|
||||
return getConversationController().get(pubkey);
|
||||
}
|
||||
|
|
|
@ -98,6 +98,11 @@ export interface MessageAttributes {
|
|||
*/
|
||||
dataExtractionNotification?: DataExtractionNotificationMsg;
|
||||
|
||||
/**
|
||||
* For displaying a message to notifying when a request has been accepted.
|
||||
*/
|
||||
messageRequestResponse?: MessageRequestResponseMsg;
|
||||
|
||||
/**
|
||||
* This field is used for unsending messages and used in sending unsend message requests.
|
||||
*/
|
||||
|
@ -117,6 +122,11 @@ export interface DataExtractionNotificationMsg {
|
|||
referencedAttachmentTimestamp: number; // the attachment timestamp he screenshot
|
||||
}
|
||||
|
||||
export interface MessageRequestResponseMsg {
|
||||
source: string;
|
||||
isApproved: boolean;
|
||||
}
|
||||
|
||||
export enum MessageDirection {
|
||||
outgoing = 'outgoing',
|
||||
incoming = 'incoming',
|
||||
|
@ -129,6 +139,17 @@ export type PropsForDataExtractionNotification = DataExtractionNotificationMsg &
|
|||
isUnread: boolean;
|
||||
};
|
||||
|
||||
export type PropsForMessageRequestResponse = MessageRequestResponseMsg & {
|
||||
conversationId?: string;
|
||||
name?: string;
|
||||
messageId: string;
|
||||
receivedAt?: number;
|
||||
isUnread: boolean;
|
||||
isApproved?: boolean;
|
||||
publicKey?: string;
|
||||
source?: string;
|
||||
};
|
||||
|
||||
export type MessageGroupUpdate = {
|
||||
left?: Array<string>;
|
||||
joined?: Array<string>;
|
||||
|
@ -172,6 +193,11 @@ export interface MessageAttributesOptionals {
|
|||
source: string;
|
||||
referencedAttachmentTimestamp: number;
|
||||
};
|
||||
messageRequestResponse?: {
|
||||
/** 1 means approved, 0 means unapproved. */
|
||||
isApproved?: number;
|
||||
publicKey?: string;
|
||||
};
|
||||
unread?: number;
|
||||
group?: any;
|
||||
timestamp?: number;
|
||||
|
|
|
@ -111,6 +111,18 @@ export async function handleClosedGroupControlMessage(
|
|||
return;
|
||||
}
|
||||
if (type === Type.NEW) {
|
||||
if (
|
||||
getConversationController()
|
||||
.get(envelope.senderIdentity)
|
||||
.isApproved() == false
|
||||
) {
|
||||
window?.log?.info(
|
||||
'Received new closed group message from an unapproved sender -- dropping message.'
|
||||
);
|
||||
// TODO: remove console output
|
||||
console.warn('Received unapproved closed group invite msg');
|
||||
return;
|
||||
}
|
||||
await handleNewClosedGroup(envelope, groupUpdate);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -146,6 +146,26 @@ const handleContactReceived = async (
|
|||
) {
|
||||
if (contactReceived.isApproved) {
|
||||
await contactConvo.setIsApproved(Boolean(contactReceived.isApproved));
|
||||
|
||||
if (contactReceived.didApproveMe) {
|
||||
await contactConvo.setDidApproveMe(Boolean(contactReceived.didApproveMe));
|
||||
|
||||
// if source of the sync matches conversationId
|
||||
contactConvo.addSingleMessage({
|
||||
conversationId: contactConvo.get('id'),
|
||||
source: envelope.source,
|
||||
type: 'outgoing', // mark it as outgoing just so it appears below our sent attachment
|
||||
sent_at: _.toNumber(envelope.timestamp), // TODO: maybe add timestamp to messageRequestResponse? confirm it doesn't exist first
|
||||
received_at: Date.now(),
|
||||
messageRequestResponse: {
|
||||
isApproved: 1,
|
||||
publicKey: UserUtils.getOurPubKeyStrFromCache(), // it's a sync therefore the pubkey would be ours
|
||||
},
|
||||
unread: 1, // 1 means unread
|
||||
expireTimer: 0,
|
||||
});
|
||||
contactConvo.updateLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (contactReceived.isBlocked) {
|
||||
|
|
|
@ -3,11 +3,11 @@ import { handleDataMessage } from './dataMessage';
|
|||
|
||||
import { removeFromCache, updateCache } from './cache';
|
||||
import { SignalService } from '../protobuf';
|
||||
import * as Lodash from 'lodash';
|
||||
import _, * as Lodash from 'lodash';
|
||||
import { PubKey } from '../session/types';
|
||||
|
||||
import { BlockedNumberController } from '../util/blockedNumberController';
|
||||
import { GroupUtils, UserUtils } from '../session/utils';
|
||||
import { GroupUtils, ToastUtils, UserUtils } from '../session/utils';
|
||||
import { fromHexToArray, toHex } from '../session/utils/String';
|
||||
import { concatUInt8Array, getSodium } from '../session/crypto';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
|
@ -17,12 +17,7 @@ import { ConversationTypeEnum } from '../models/conversation';
|
|||
import { removeMessagePadding } from '../session/crypto/BufferPadding';
|
||||
import { perfEnd, perfStart } from '../session/utils/Performance';
|
||||
import { getAllCachedECKeyPair } from './closedGroups';
|
||||
import { getMessageBySenderAndTimestamp } from '../data/data';
|
||||
import { handleCallMessage } from './callMessage';
|
||||
import {
|
||||
deleteMessagesFromSwarmAndCompletelyLocally,
|
||||
deleteMessagesFromSwarmAndMarkAsDeletedLocally,
|
||||
} from '../interactions/conversations/unsendingInteractions';
|
||||
import { SettingsKey } from '../data/settings-key';
|
||||
|
||||
export async function handleContentMessage(envelope: EnvelopePlus, messageHash: string) {
|
||||
|
@ -406,6 +401,13 @@ export async function innerHandleContentMessage(
|
|||
if (content.callMessage && window.lokiFeatureFlags?.useCallMessage) {
|
||||
await handleCallMessage(envelope, content.callMessage as SignalService.CallMessage);
|
||||
}
|
||||
if (content.messageRequestResponse) {
|
||||
console.warn('received message request response');
|
||||
await handleMessageRequestResponse(
|
||||
envelope,
|
||||
content.messageRequestResponse as SignalService.MessageRequestResponse
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
window?.log?.warn(e);
|
||||
}
|
||||
|
@ -509,7 +511,6 @@ async function handleUnsendMessage(envelope: EnvelopePlus, unsendMessage: Signal
|
|||
return;
|
||||
}
|
||||
if (!unsendMessage) {
|
||||
//#region early exit conditions
|
||||
window?.log?.error('handleUnsendMessage: Invalid parameters -- dropping message.');
|
||||
await removeFromCache(envelope);
|
||||
|
||||
|
@ -521,40 +522,63 @@ async function handleUnsendMessage(envelope: EnvelopePlus, unsendMessage: Signal
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const messageToDelete = await getMessageBySenderAndTimestamp({
|
||||
source: messageAuthor,
|
||||
timestamp: Lodash.toNumber(timestamp),
|
||||
});
|
||||
const messageHash = messageToDelete?.get('messageHash');
|
||||
//#endregion
|
||||
/**
|
||||
* Sets approval fields for conversation depending on response's values. If request is approving, pushes notification and
|
||||
*/
|
||||
async function handleMessageRequestResponse(
|
||||
envelope: EnvelopePlus,
|
||||
messageRequestResponse: SignalService.MessageRequestResponse
|
||||
) {
|
||||
const { isApproved, publicKey } = messageRequestResponse;
|
||||
|
||||
//#region executing deletion
|
||||
if (messageHash && messageToDelete) {
|
||||
window.log.info('handleUnsendMessage: got a request to delete ', messageHash);
|
||||
const conversation = getConversationController().get(messageToDelete.get('conversationId'));
|
||||
if (!conversation) {
|
||||
await removeFromCache(envelope);
|
||||
|
||||
return;
|
||||
}
|
||||
if (messageToDelete.getSource() === UserUtils.getOurPubKeyStrFromCache()) {
|
||||
// a message we sent is completely removed when we get a unsend request
|
||||
void deleteMessagesFromSwarmAndCompletelyLocally(conversation, [messageToDelete]);
|
||||
} else {
|
||||
void deleteMessagesFromSwarmAndMarkAsDeletedLocally(conversation, [messageToDelete]);
|
||||
}
|
||||
} else {
|
||||
window.log.info(
|
||||
'handleUnsendMessage: got a request to delete an unknown messageHash:',
|
||||
messageHash,
|
||||
' and found messageToDelete:',
|
||||
messageToDelete?.id
|
||||
);
|
||||
if (!messageRequestResponse) {
|
||||
window?.log?.error('handleMessageRequestResponse: Invalid parameters -- dropping message.');
|
||||
await removeFromCache(envelope);
|
||||
return;
|
||||
}
|
||||
await removeFromCache(envelope);
|
||||
|
||||
//#endregion
|
||||
const convoId = toHex(publicKey);
|
||||
|
||||
// TODO: commenting out, including in one larger function for now
|
||||
// await updateConversationDidApproveMe(toHex(publicKey), isApproved);
|
||||
|
||||
const conversationToApprove = getConversationController().get(convoId);
|
||||
|
||||
if (!conversationToApprove || conversationToApprove.didApproveMe() === isApproved) {
|
||||
window?.log?.info(
|
||||
'Conversation already contains the correct value for the didApproveMe field.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Maybe move this to conversation interactions
|
||||
await conversationToApprove.setIsApproved(isApproved);
|
||||
await conversationToApprove.setDidApproveMe(isApproved);
|
||||
if (isApproved === true) {
|
||||
ToastUtils.pushMessageRequestAccepted();
|
||||
|
||||
// Conversation was not approved before so a sync is needed
|
||||
conversationToApprove.addSingleMessage({
|
||||
conversationId: conversationToApprove.get('id'),
|
||||
source: envelope.source,
|
||||
type: 'outgoing', // mark it as outgoing just so it appears below our sent attachment
|
||||
sent_at: _.toNumber(envelope.timestamp), // TODO: maybe add timestamp to messageRequestResponse? confirm it doesn't exist first
|
||||
received_at: Date.now(),
|
||||
messageRequestResponse: {
|
||||
isApproved: 1,
|
||||
publicKey: convoId,
|
||||
},
|
||||
unread: 1, // 1 means unread
|
||||
expireTimer: 0,
|
||||
});
|
||||
conversationToApprove.updateLastMessage();
|
||||
}
|
||||
|
||||
// await forceSyncConfigurationNowIfNeeded();
|
||||
|
||||
await removeFromCache(envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -223,10 +223,10 @@ async function handleRegularMessage(
|
|||
if (type === 'outgoing') {
|
||||
await handleSyncedReceipts(message, conversation);
|
||||
|
||||
if (window.lokiFeatureFlags.useMessageRequests) {
|
||||
// assumes sync receipts are always from linked device outgoings
|
||||
await conversation.setIsApproved(true);
|
||||
}
|
||||
// if (window.lokiFeatureFlags.useMessageRequests) {
|
||||
// // assumes sync receipts are always from linked device outgoings
|
||||
// await conversation.setIsApproved(true);
|
||||
// }
|
||||
}
|
||||
|
||||
const conversationActiveAt = conversation.get('active_at');
|
||||
|
|
|
@ -95,6 +95,7 @@ export class ConfigurationMessageContact {
|
|||
public profileKey?: Uint8Array;
|
||||
public isApproved?: boolean;
|
||||
public isBlocked?: boolean;
|
||||
public didApproveMe?: boolean;
|
||||
|
||||
public constructor({
|
||||
publicKey,
|
||||
|
@ -103,6 +104,7 @@ export class ConfigurationMessageContact {
|
|||
profileKey,
|
||||
isApproved,
|
||||
isBlocked,
|
||||
didApproveMe
|
||||
}: {
|
||||
publicKey: string;
|
||||
displayName: string;
|
||||
|
@ -110,6 +112,7 @@ export class ConfigurationMessageContact {
|
|||
profileKey?: Uint8Array;
|
||||
isApproved?: boolean;
|
||||
isBlocked?: boolean;
|
||||
didApproveMe?: boolean;
|
||||
}) {
|
||||
this.publicKey = publicKey;
|
||||
this.displayName = displayName;
|
||||
|
@ -117,6 +120,7 @@ export class ConfigurationMessageContact {
|
|||
this.profileKey = profileKey;
|
||||
this.isApproved = isApproved;
|
||||
this.isBlocked = isBlocked;
|
||||
this.didApproveMe = didApproveMe;
|
||||
|
||||
// will throw if public key is invalid
|
||||
PubKey.cast(publicKey);
|
||||
|
@ -141,6 +145,7 @@ export class ConfigurationMessageContact {
|
|||
profileKey: this.profileKey,
|
||||
isApproved: this.isApproved,
|
||||
isBlocked: this.isBlocked,
|
||||
didApproveMe: this.didApproveMe
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { OpenGroupRequestCommonType } from '../apis/open_group_api/opengroupV2/A
|
|||
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
|
||||
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
|
||||
import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage';
|
||||
import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse';
|
||||
|
||||
type ClosedGroupMessageType =
|
||||
| ClosedGroupVisibleMessage
|
||||
|
@ -32,7 +33,8 @@ type ClosedGroupMessageType =
|
|||
| ExpirationTimerUpdateMessage
|
||||
| ClosedGroupEncryptionPairMessage
|
||||
| UnsendMessage
|
||||
| ClosedGroupEncryptionPairRequestMessage;
|
||||
| ClosedGroupEncryptionPairRequestMessage
|
||||
| MessageRequestResponse;
|
||||
|
||||
// ClosedGroupEncryptionPairReplyMessage must be sent to a user pubkey. Not a group.
|
||||
|
||||
|
|
|
@ -212,6 +212,10 @@ export function pushTooManyMembers() {
|
|||
pushToastError('tooManyMembers', window.i18n('closedGroupMaxSize'));
|
||||
}
|
||||
|
||||
export function pushMessageRequestPending() {
|
||||
pushToastInfo('messageRequestPending', window.i18n('messageRequestPending'));
|
||||
}
|
||||
|
||||
export function pushUnblockToSend() {
|
||||
pushToastInfo('unblockToSend', window.i18n('unblockToSend'));
|
||||
}
|
||||
|
@ -232,6 +236,11 @@ export function pushDeleted() {
|
|||
pushToastSuccess('deleted', window.i18n('deleted'), undefined, 'check');
|
||||
}
|
||||
|
||||
export function pushMessageRequestAccepted() {
|
||||
// TODO: translation
|
||||
pushToastSuccess('requestAccepted', 'message request accepted', undefined, undefined);
|
||||
}
|
||||
|
||||
export function pushCannotRemoveCreatorFromGroup() {
|
||||
pushToastWarning(
|
||||
'cannotRemoveCreatorFromGroup',
|
||||
|
|
|
@ -28,6 +28,7 @@ import { getV2OpenGroupRoom } from '../../data/opengroups';
|
|||
import { getCompleteUrlFromRoom } from '../apis/open_group_api/utils/OpenGroupUtils';
|
||||
import { DURATION } from '../constants';
|
||||
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
|
||||
import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse';
|
||||
|
||||
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
|
||||
|
||||
|
@ -197,6 +198,7 @@ const getValidContacts = (convos: Array<ConversationModel>) => {
|
|||
profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact,
|
||||
isApproved: c.isApproved(),
|
||||
isBlocked: c.isBlocked(),
|
||||
didApproveMe: c.didApproveMe(),
|
||||
});
|
||||
} catch (e) {
|
||||
window?.log.warn('getValidContacts', e);
|
||||
|
@ -307,6 +309,7 @@ export type SyncMessageType =
|
|||
| VisibleMessage
|
||||
| ExpirationTimerUpdateMessage
|
||||
| ConfigurationMessage
|
||||
| MessageRequestResponse
|
||||
| UnsendMessage;
|
||||
|
||||
export const buildSyncMessage = (
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
MessageDeliveryStatus,
|
||||
MessageModelType,
|
||||
PropsForDataExtractionNotification,
|
||||
PropsForMessageRequestResponse,
|
||||
} from '../../models/messageType';
|
||||
import { perfEnd, perfStart } from '../../session/utils/Performance';
|
||||
import { omit } from 'lodash';
|
||||
|
@ -32,6 +33,7 @@ export type MessageModelPropsWithoutConvoProps = {
|
|||
propsForDataExtractionNotification?: PropsForDataExtractionNotification;
|
||||
propsForGroupUpdateMessage?: PropsForGroupUpdate;
|
||||
propsForCallNotification?: PropsForCallNotification;
|
||||
propsForMessageRequestResponse?: PropsForMessageRequestResponse;
|
||||
};
|
||||
|
||||
export type MessageModelPropsWithConvoProps = SortedMessageModelProps & {
|
||||
|
@ -252,6 +254,7 @@ export interface ReduxConversationType {
|
|||
|
||||
isPinned?: boolean;
|
||||
isApproved?: boolean;
|
||||
didApproveMe?: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationForConvoOption {
|
||||
|
|
|
@ -171,6 +171,7 @@ export type MessagePropsType =
|
|||
| 'group-notification'
|
||||
| 'group-invitation'
|
||||
| 'data-extraction'
|
||||
| 'message-request-response'
|
||||
| 'timer-notification'
|
||||
| 'regular-message'
|
||||
| 'unread-indicator'
|
||||
|
@ -208,6 +209,17 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
|
|||
};
|
||||
}
|
||||
|
||||
if (msg.propsForMessageRequestResponse) {
|
||||
return {
|
||||
showUnreadIndicator: isFirstUnread,
|
||||
showDateBreak,
|
||||
message: {
|
||||
messageType: 'message-request-response',
|
||||
props: { ...msg.propsForMessageRequestResponse, messageId: msg.propsForMessage.id },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (msg.propsForGroupInvitation) {
|
||||
return {
|
||||
showUnreadIndicator: isFirstUnread,
|
||||
|
@ -412,6 +424,12 @@ export const getSortedConversations = createSelector(
|
|||
_getSortedConversations
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sortedConversations List of conversations that are valid for both requests and regular conversation inbox
|
||||
* @param isMessageRequestEnabled Apply message request filtering.
|
||||
* @returns A list of message request conversations.
|
||||
*/
|
||||
const _getConversationRequests = (
|
||||
sortedConversations: Array<ReduxConversationType>,
|
||||
isMessageRequestEnabled?: boolean
|
||||
|
@ -419,7 +437,14 @@ const _getConversationRequests = (
|
|||
const pushToMessageRequests =
|
||||
isMessageRequestEnabled && window?.lokiFeatureFlags?.useMessageRequests;
|
||||
return _.filter(sortedConversations, conversation => {
|
||||
return pushToMessageRequests && !conversation.isApproved && !conversation.isBlocked;
|
||||
console.warn({ conversation });
|
||||
return (
|
||||
pushToMessageRequests &&
|
||||
!conversation.isApproved &&
|
||||
!conversation.isBlocked &&
|
||||
conversation.isPrivate &&
|
||||
!conversation.isMe
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -442,6 +467,7 @@ const _getPrivateContactsPubkeys = (
|
|||
conversation.isPrivate &&
|
||||
!conversation.isBlocked &&
|
||||
!conversation.isMe &&
|
||||
(conversation.didApproveMe || !pushToMessageRequests) &&
|
||||
(conversation.isApproved || !pushToMessageRequests) &&
|
||||
Boolean(conversation.activeAt)
|
||||
);
|
||||
|
|
|
@ -254,7 +254,7 @@ export type LocalizerKeys =
|
|||
| 'editMenuDeleteContact'
|
||||
| 'hideMenuBarTitle'
|
||||
| 'imageCaptionIconAlt'
|
||||
| 'blockAll'
|
||||
| 'clearAll'
|
||||
| 'sendRecoveryPhraseTitle'
|
||||
| 'multipleJoinedTheGroup'
|
||||
| 'databaseError'
|
||||
|
@ -467,4 +467,8 @@ export type LocalizerKeys =
|
|||
| 'trimDatabaseConfirmationBody'
|
||||
| 'reportAsSpam'
|
||||
| 'respondingToRequestWarning'
|
||||
| 'messageRequestPending'
|
||||
| 'messageRequestAccepted'
|
||||
| 'messageRequestAcceptedOurs'
|
||||
| 'declineRequestMessage'
|
||||
| 'reportIssue';
|
||||
|
|
Loading…
Reference in New Issue