Adding blocking of individual requests and syncing of block to devices. Added approval by replying to a message.

pull/2000/head
Warrick Corfe-Tan 3 years ago
parent 4ad14e4c5b
commit c3924f85a9

@ -443,5 +443,9 @@
"messageDeletedPlaceholder": "This message has been deleted", "messageDeletedPlaceholder": "This message has been deleted",
"messageDeleted": "Message deleted", "messageDeleted": "Message deleted",
"surveyTitle": "Take our Session Survey", "surveyTitle": "Take our Session Survey",
"goToOurSurvey": "Go to our survey" "goToOurSurvey": "Go to our survey",
"blockAll": "Block All",
"messageRequests": "Message Requests",
"requestsSubtitle": "Pending Requests",
"requestsPlaceHolder": "No requests"
} }

@ -835,6 +835,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToLokiSchemaVersion14, updateToLokiSchemaVersion14,
updateToLokiSchemaVersion15, updateToLokiSchemaVersion15,
updateToLokiSchemaVersion16, updateToLokiSchemaVersion16,
updateToLokiSchemaVersion17,
]; ];
function updateToLokiSchemaVersion1(currentVersion, db) { function updateToLokiSchemaVersion1(currentVersion, db) {
@ -1228,6 +1229,23 @@ function updateToLokiSchemaVersion16(currentVersion, db) {
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`); console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
} }
function updateToLokiSchemaVersion17(currentVersion, db) {
const targetVersion = 17;
if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToLokiSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
db.exec(`
ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN isApproved BOOLEAN;
`);
writeLokiSchemaVersion(targetVersion, db);
})();
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
}
function writeLokiSchemaVersion(newVersion, db) { function writeLokiSchemaVersion(newVersion, db) {
db.prepare( db.prepare(
`INSERT INTO loki_schema( `INSERT INTO loki_schema(
@ -1604,8 +1622,8 @@ function updateConversation(data) {
profileName, profileName,
} = data; } = data;
// TODO: msgreq - remove
console.log({ usrData: data }); console.log({ usrData: data });
console.log({ usrDataTrace: console.trace() });
console.log('usrData@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'); console.log('usrData@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@');
globalInstance globalInstance
@ -1619,7 +1637,7 @@ function updateConversation(data) {
name = $name, name = $name,
isApproved = $isApproved, isApproved = $isApproved,
profileName = $profileName profileName = $profileName
WHERE id = $id;` WHERE id = $id;`
) )
.run({ .run({
id, id,

@ -170,7 +170,8 @@ message ConfigurationMessage {
required string name = 2; required string name = 2;
optional string profilePicture = 3; optional string profilePicture = 3;
optional bytes profileKey = 4; optional bytes profileKey = 4;
optional bool isApproved = 5; optional bool isApproved = 5;
optional bool isBlocked = 6;
} }
repeated ClosedGroup closedGroups = 1; repeated ClosedGroup closedGroups = 1;

@ -31,6 +31,7 @@ import { Flex } from './basic/Flex';
import { SessionButton, SessionButtonColor } from './session/SessionButton'; import { SessionButton, SessionButtonColor } from './session/SessionButton';
import { getConversationById } from '../data/data'; import { getConversationById } from '../data/data';
import { syncConfigurationIfNeeded } from '../session/utils/syncUtils'; import { syncConfigurationIfNeeded } from '../session/utils/syncUtils';
import { BlockedNumberController } from '../util';
// tslint:disable-next-line: no-empty-interface // tslint:disable-next-line: no-empty-interface
export interface ConversationListItemProps extends ReduxConversationType {} export interface ConversationListItemProps extends ReduxConversationType {}
@ -284,13 +285,16 @@ const ConversationListItem = (props: Props) => {
); );
/** /**
* deletes the conversation * Removes conversation from requests list,
* adds ID to block list, syncs the block with linked devices.
*/ */
const handleConversationDecline = async () => { const handleConversationBlock = async () => {
// const convoToDecline = await getConversationById(conversationId); const convoToBlock = await getConversationById(conversationId);
// convoToDecline?.setIsApproved(false); if (!convoToBlock) {
// await getConversationController().deleteContact(conversationId); // TODO: might be unnecessary window?.log?.error('Unable to find conversation to be blocked.');
console.warn('decline'); }
await BlockedNumberController.block(convoToBlock?.id);
await syncConfigurationIfNeeded(true);
}; };
/** /**
@ -302,7 +306,6 @@ const ConversationListItem = (props: Props) => {
console.warn({ convoAfterSetIsApproved: conversationToApprove }); console.warn({ convoAfterSetIsApproved: conversationToApprove });
// TODO: Send sync message to other devices. Using config message // TODO: Send sync message to other devices. Using config message
await syncConfigurationIfNeeded(true); await syncConfigurationIfNeeded(true);
}; };
@ -364,10 +367,10 @@ const ConversationListItem = (props: Props) => {
justifyContent="flex-end" justifyContent="flex-end"
> >
<SessionButton <SessionButton
onClick={handleConversationDecline} onClick={handleConversationBlock}
buttonColor={SessionButtonColor.Danger} buttonColor={SessionButtonColor.Danger}
> >
Decline Block
</SessionButton> </SessionButton>
<SessionButton <SessionButton
buttonColor={SessionButtonColor.Green} buttonColor={SessionButtonColor.Green}

@ -29,6 +29,7 @@ import { SNodeAPI } from '../../session/snode_api';
import { clearSearch, search, updateSearchTerm } from '../../state/ducks/search'; import { clearSearch, search, updateSearchTerm } from '../../state/ducks/search';
import _ from 'lodash'; import _ from 'lodash';
import { MessageRequestsBanner } from './MessageRequestsBanner'; import { MessageRequestsBanner } from './MessageRequestsBanner';
import { BlockedNumberController } from '../../util';
export interface Props { export interface Props {
searchTerm: string; searchTerm: string;
@ -85,8 +86,13 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
public renderRow = ({ index, key, style }: RowRendererParamsType): JSX.Element | null => { public renderRow = ({ index, key, style }: RowRendererParamsType): JSX.Element | null => {
const { conversations } = this.props; const { conversations } = this.props;
const approvedConversations = conversations?.filter(c => Boolean(c.isApproved) === true); const conversationsToShow = conversations?.filter(async c => {
if (!conversations || !approvedConversations) { return (
Boolean(c.isApproved) === true &&
(await BlockedNumberController.isBlockedAsync(c.id)) === false
);
});
if (!conversations || !conversationsToShow) {
throw new Error('renderRow: Tried to render without conversations'); throw new Error('renderRow: Tried to render without conversations');
} }
@ -95,8 +101,8 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
window.inboxStore?.getState().userConfig.messageRequests === true; window.inboxStore?.getState().userConfig.messageRequests === true;
let conversation; let conversation;
if (approvedConversations?.length) { if (conversationsToShow?.length) {
conversation = approvedConversations[index]; conversation = conversationsToShow[index];
} }
if (!conversation) { if (!conversation) {
@ -298,11 +304,8 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
this.handleToggleOverlay(undefined); this.handleToggleOverlay(undefined);
}} }}
onButtonClick={async () => { onButtonClick={async () => {
// decline all convos // TODO: msgrequest iterate all convos and block
// close modal
// this.state.approvedConversations.map(async(convo) => {
console.warn('Test'); console.warn('Test');
// } )
}} }}
searchTerm={searchTerm} searchTerm={searchTerm}
searchResults={searchResults} searchResults={searchResults}

@ -83,10 +83,12 @@ export const CirclularIcon = (props: { iconType: SessionIconType; iconSize: Sess
export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => { export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => {
const { handleOnClick } = props; const { handleOnClick } = props;
const convos = useSelector(getLeftPaneLists).conversations; const convos = useSelector(getLeftPaneLists).conversationRequests;
const pendingRequestsCount = (convos.filter(c => c.isApproved !== true) || []).length; // const pendingRequestsCount = (
// convos.filter(c => Boolean(c.isApproved) === false && !Boolean(c.isBlocked)) || []
// ).length;
if (!pendingRequestsCount) { if (!convos.length) {
return null; return null;
} }
@ -95,7 +97,7 @@ export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => {
<CirclularIcon iconType="messageRequest" iconSize="medium" /> <CirclularIcon iconType="messageRequest" iconSize="medium" />
<StyledMessageRequestBannerHeader>Message Requests</StyledMessageRequestBannerHeader> <StyledMessageRequestBannerHeader>Message Requests</StyledMessageRequestBannerHeader>
<StyledUnreadCounter> <StyledUnreadCounter>
<div>{pendingRequestsCount}</div> <div>{convos.length || 0}</div>
</StyledUnreadCounter> </StyledUnreadCounter>
</StyledMessageRequestBanner> </StyledMessageRequestBanner>
); );

@ -141,10 +141,10 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
placeholder = window.i18n('createClosedGroupPlaceholder'); placeholder = window.i18n('createClosedGroupPlaceholder');
break; break;
case SessionClosableOverlayType.MessageRequests: case SessionClosableOverlayType.MessageRequests:
title = 'Message Requests'; title = window.i18n('messageRequests');
buttonText = 'Decline All'; buttonText = window.i18n('blockAll');
subtitle = 'Pending Requests'; subtitle = window.i18n('requestsSubtitle');
placeholder = 'placeholder'; placeholder = window.i18n('requestsPlaceholder');
break; break;
default: default:
} }
@ -291,16 +291,18 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
} }
} }
/**
* A request needs to be be unapproved and not blocked to be valid.
* @returns List of message request items
*/
const MessageRequestList = () => { const MessageRequestList = () => {
// get all conversations with (accepted / known)
const lists = useSelector(getLeftPaneLists); const lists = useSelector(getLeftPaneLists);
const unapprovedConversations = lists?.conversations.filter(c => { // const validConversationRequests = lists?.conversations.filter(c => {
return !c.isApproved; const validConversationRequests = lists?.conversationRequests;
}) as Array<ConversationListItemProps>; console.warn({ unapprovedConversationsListConstructor: validConversationRequests });
console.warn({ unapprovedConversationsListConstructor: unapprovedConversations });
return ( return (
<div className="message-request-list__container"> <div className="message-request-list__container">
{unapprovedConversations.map(conversation => { {validConversationRequests.map(conversation => {
return <MessageRequestListItem conversation={conversation} />; return <MessageRequestListItem conversation={conversation} />;
})} })}
</div> </div>

@ -50,6 +50,7 @@ import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsMana
import { IMAGE_JPEG } from '../types/MIME'; import { IMAGE_JPEG } from '../types/MIME';
import { UnsendMessage } from '../session/messages/outgoing/controlMessage/UnsendMessage'; import { UnsendMessage } from '../session/messages/outgoing/controlMessage/UnsendMessage';
import { getLatestTimestampOffset, networkDeleteMessages } from '../session/snode_api/SNodeAPI'; import { getLatestTimestampOffset, networkDeleteMessages } from '../session/snode_api/SNodeAPI';
import { syncConfigurationIfNeeded } from '../session/utils/syncUtils';
export enum ConversationTypeEnum { export enum ConversationTypeEnum {
GROUP = 'group', GROUP = 'group',
@ -715,9 +716,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const sentAt = message.get('sent_at'); const sentAt = message.get('sent_at');
// TODO: for debugging // TODO: msgreq for debugging
if (message.get('body')?.includes('unapprove')) { const unapprove = message.get('body')?.includes('unapprove');
console.warn('setting to unapprove'); if (unapprove) {
await this.setIsApproved(false); await this.setIsApproved(false);
} }
@ -740,6 +741,13 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
lokiProfile: UserUtils.getOurProfile(), lokiProfile: UserUtils.getOurProfile(),
}; };
const updateApprovalNeeded =
!this.isApproved() && (this.isPrivate() || this.isMediumGroup() || this.isClosedGroup());
if (updateApprovalNeeded && !unapprove) {
this.setIsApproved(true);
await syncConfigurationIfNeeded(true);
}
if (this.isOpenGroupV2()) { if (this.isOpenGroupV2()) {
const chatMessageOpenGroupV2 = new OpenGroupVisibleMessage(chatMessageParams); const chatMessageOpenGroupV2 = new OpenGroupVisibleMessage(chatMessageParams);
const roomInfos = this.toOpenGroupV2(); const roomInfos = this.toOpenGroupV2();
@ -1506,7 +1514,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
public isApproved() { public isApproved() {
return this.get('isApproved'); return Boolean(this.get('isApproved'));
} }
public getTitle() { public getTitle() {

@ -11,6 +11,7 @@ import { getConversationController } from '../session/conversations';
import { UserUtils } from '../session/utils'; import { UserUtils } from '../session/utils';
import { toHex } from '../session/utils/String'; import { toHex } from '../session/utils/String';
import { configurationMessageReceived, trigger } from '../shims/events'; import { configurationMessageReceived, trigger } from '../shims/events';
import { BlockedNumberController } from '../util';
import { removeFromCache } from './cache'; import { removeFromCache } from './cache';
import { handleNewClosedGroup } from './closedGroups'; import { handleNewClosedGroup } from './closedGroups';
import { updateProfileOneAtATime } from './dataMessage'; import { updateProfileOneAtATime } from './dataMessage';
@ -111,33 +112,42 @@ async function handleGroupsAndContactsFromConfigMessage(
} }
} }
if (configMessage.contacts?.length) { if (configMessage.contacts?.length) {
await Promise.all( await Promise.all(configMessage.contacts.map(async c => handleContactReceived(c, envelope)));
configMessage.contacts.map(async c => {
try {
if (!c.publicKey) {
return;
}
const contactConvo = await getConversationController().getOrCreateAndWait(
toHex(c.publicKey),
ConversationTypeEnum.PRIVATE
);
const profile = {
displayName: c.name,
profilePictre: c.profilePicture,
};
// updateProfile will do a commit for us
contactConvo.set('active_at', _.toNumber(envelope.timestamp));
contactConvo.setIsApproved(Boolean(c.isApproved));
await updateProfileOneAtATime(contactConvo, profile, c.profileKey);
} catch (e) {
window?.log?.warn('failed to handle a new closed group from configuration message');
}
})
);
} }
} }
const handleContactReceived = async (
contactReceived: SignalService.ConfigurationMessage.IContact,
envelope: EnvelopePlus
) => {
try {
if (!contactReceived.publicKey) {
return;
}
const contactConvo = await getConversationController().getOrCreateAndWait(
toHex(contactReceived.publicKey),
ConversationTypeEnum.PRIVATE
);
const profile = {
displayName: contactReceived.name,
profilePictre: contactReceived.profilePicture,
};
// updateProfile will do a commit for us
contactConvo.set('active_at', _.toNumber(envelope.timestamp));
contactConvo.setIsApproved(Boolean(contactReceived.isApproved));
if (contactReceived.isBlocked === true) {
await BlockedNumberController.block(contactConvo.id);
} else {
await BlockedNumberController.unblock(contactConvo.id);
}
await updateProfileOneAtATime(contactConvo, profile, contactReceived.profileKey);
} catch (e) {
window?.log?.warn('failed to handle a new closed group from configuration message');
}
};
export async function handleConfigurationMessage( export async function handleConfigurationMessage(
envelope: EnvelopePlus, envelope: EnvelopePlus,
configurationMessage: SignalService.ConfigurationMessage configurationMessage: SignalService.ConfigurationMessage

@ -262,6 +262,12 @@ export class ConversationController {
return Array.from(this.conversations.models); return Array.from(this.conversations.models);
} }
public getConversationRequests(): Array<ConversationModel> {
return Array.from(this.conversations.models).filter(
conversation => conversation.isApproved() && !conversation.isBlocked
);
}
public unsafeDelete(convo: ConversationModel) { public unsafeDelete(convo: ConversationModel) {
this.conversations.remove(convo); this.conversations.remove(convo);
} }

@ -94,6 +94,7 @@ export class ConfigurationMessageContact {
public profilePictureURL?: string; public profilePictureURL?: string;
public profileKey?: Uint8Array; public profileKey?: Uint8Array;
public isApproved?: boolean; public isApproved?: boolean;
public isBlocked?: boolean;
public constructor({ public constructor({
publicKey, publicKey,
@ -101,18 +102,21 @@ export class ConfigurationMessageContact {
profilePictureURL, profilePictureURL,
profileKey, profileKey,
isApproved, isApproved,
isBlocked,
}: { }: {
publicKey: string; publicKey: string;
displayName: string; displayName: string;
profilePictureURL?: string; profilePictureURL?: string;
profileKey?: Uint8Array; profileKey?: Uint8Array;
isApproved?: boolean; isApproved?: boolean;
isBlocked?: boolean;
}) { }) {
this.publicKey = publicKey; this.publicKey = publicKey;
this.displayName = displayName; this.displayName = displayName;
this.profilePictureURL = profilePictureURL; this.profilePictureURL = profilePictureURL;
this.profileKey = profileKey; this.profileKey = profileKey;
this.isApproved = isApproved; this.isApproved = isApproved;
this.isBlocked = isBlocked;
// will throw if public key is invalid // will throw if public key is invalid
PubKey.cast(publicKey); PubKey.cast(publicKey);
@ -136,6 +140,7 @@ export class ConfigurationMessageContact {
profilePicture: this.profilePictureURL, profilePicture: this.profilePictureURL,
profileKey: this.profileKey, profileKey: this.profileKey,
isApproved: this.isApproved, isApproved: this.isApproved,
isBlocked: this.isBlocked,
}); });
} }
} }

@ -164,7 +164,7 @@ const getValidClosedGroups = async (convos: Array<ConversationModel>) => {
const getValidContacts = (convos: Array<ConversationModel>) => { const getValidContacts = (convos: Array<ConversationModel>) => {
// Filter contacts // Filter contacts
const contactsModels = convos.filter( const contactsModels = convos.filter(
c => !!c.get('active_at') && c.getLokiProfile()?.displayName && c.isPrivate() && !c.isBlocked() c => !!c.get('active_at') && c.getLokiProfile()?.displayName && c.isPrivate()
); );
const contacts = contactsModels.map(c => { const contacts = contactsModels.map(c => {
@ -201,6 +201,7 @@ const getValidContacts = (convos: Array<ConversationModel>) => {
profilePictureURL: c.get('avatarPointer'), profilePictureURL: c.get('avatarPointer'),
profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact, profileKey: !profileKeyForContact?.length ? undefined : profileKeyForContact,
isApproved: c.isApproved(), isApproved: c.isApproved(),
isBlocked: c.isBlocked(),
}); });
} catch (e) { } catch (e) {
window?.log.warn('getValidContacts', e); window?.log.warn('getValidContacts', e);

@ -275,6 +275,7 @@ export const _getLeftPaneLists = (
): { ): {
conversations: Array<ReduxConversationType>; conversations: Array<ReduxConversationType>;
contacts: Array<ReduxConversationType>; contacts: Array<ReduxConversationType>;
conversationRequests: Array<ReduxConversationType>;
unreadCount: number; unreadCount: number;
} => { } => {
const values = Object.values(lookup); const values = Object.values(lookup);
@ -282,6 +283,7 @@ export const _getLeftPaneLists = (
const conversations: Array<ReduxConversationType> = []; const conversations: Array<ReduxConversationType> = [];
const directConversations: Array<ReduxConversationType> = []; const directConversations: Array<ReduxConversationType> = [];
const conversationRequests: Array<ReduxConversationType> = [];
let unreadCount = 0; let unreadCount = 0;
for (let conversation of sorted) { for (let conversation of sorted) {
@ -317,6 +319,10 @@ export const _getLeftPaneLists = (
directConversations.push(conversation); directConversations.push(conversation);
} }
if (!conversation.isApproved && !conversation.isBlocked) {
conversationRequests.push(conversation);
}
if ( if (
unreadCount < 9 && unreadCount < 9 &&
conversation.unreadCount && conversation.unreadCount &&
@ -326,12 +332,15 @@ export const _getLeftPaneLists = (
unreadCount += conversation.unreadCount; unreadCount += conversation.unreadCount;
} }
conversations.push(conversation); if (conversation.isApproved) {
conversations.push(conversation);
}
} }
return { return {
conversations, conversations,
contacts: directConversations, contacts: directConversations,
conversationRequests,
unreadCount, unreadCount,
}; };
}; };

Loading…
Cancel
Save