Merge pull request #1277 from Bilb/fix-blocklist-handle

pull/1289/head
Audric Ackermann 5 years ago committed by GitHub
commit eb06356b26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1486,9 +1486,6 @@
if (messageReceiver) {
await messageReceiver.close();
}
const USERNAME = storage.get('number_id');
const PASSWORD = storage.get('password');
const mySignalingKey = storage.get('signaling_key');
connectCount += 1;
@ -1517,31 +1514,18 @@
);
window.lokiPublicChatAPI = null;
window.feeds = [];
messageReceiver = new textsecure.MessageReceiver(
USERNAME,
PASSWORD,
mySignalingKey,
options
);
messageReceiver = new textsecure.MessageReceiver(mySignalingKey, options);
messageReceiver.addEventListener(
'message',
window.NewReceiver.handleMessageEvent
);
window.textsecure.messaging = new textsecure.MessageSender(
USERNAME,
PASSWORD
);
window.textsecure.messaging = new textsecure.MessageSender();
return;
}
initAPIs();
await initSpecialConversations();
messageReceiver = new textsecure.MessageReceiver(
USERNAME,
PASSWORD,
mySignalingKey,
options
);
messageReceiver = new textsecure.MessageReceiver(mySignalingKey, options);
messageReceiver.addEventListener(
'message',
window.NewReceiver.handleMessageEvent
@ -1560,10 +1544,7 @@
logger: window.log,
});
window.textsecure.messaging = new textsecure.MessageSender(
USERNAME,
PASSWORD
);
window.textsecure.messaging = new textsecure.MessageSender();
// On startup after upgrading to a new version, request a contact sync
// (but only if we're not the primary device)

@ -253,11 +253,7 @@
this.trigger('change', this);
this.messageCollection.forEach(m => m.trigger('change'));
this.updateTextInputState();
if (this.isPrivate()) {
await textsecure.messaging.sendContactSyncMessage([this]);
} else {
await textsecure.messaging.sendGroupSyncMessage([this]);
}
await textsecure.messaging.sendBlockedListSyncMessage();
},
async unblock() {
if (!this.id || this.isPublic() || this.isRss()) {
@ -270,11 +266,7 @@
this.trigger('change', this);
this.messageCollection.forEach(m => m.trigger('change'));
this.updateTextInputState();
if (this.isPrivate()) {
await textsecure.messaging.sendContactSyncMessage([this]);
} else {
await textsecure.messaging.sendGroupSyncMessage([this]);
}
await textsecure.messaging.sendBlockedListSyncMessage();
},
setMessageSelectionBackdrop() {
const messageSelected = this.selectedMessages.size > 0;
@ -616,6 +608,7 @@
},
isOnline: this.isOnline(),
hasNickname: !!this.getNickname(),
isKickedFromGroup: !!this.get('isKickedFromGroup'),
selectedMessages: this.selectedMessages,
@ -628,6 +621,9 @@
onDeleteContact: () => this.deleteContact(),
onDeleteMessages: () => this.deleteMessages(),
onCloseOverlay: () => this.resetMessageSelection(),
onInviteContacts: () => {
window.Whisper.events.trigger('inviteContacts', this);
},
};
return result;

@ -1,69 +0,0 @@
/* global Whisper, Signal, Backbone */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
// list of conversations, showing user/group and last message sent
Whisper.ConversationListItemView = Whisper.View.extend({
tagName: 'div',
className() {
return `conversation-list-item contact ${this.model.cid}`;
},
templateName: 'conversation-preview',
initialize() {
this.listenTo(this.model, 'destroy', this.remove);
},
remove() {
if (this.childView) {
this.childView.remove();
this.childView = null;
}
Backbone.View.prototype.remove.call(this);
},
getProps() {
return this.model.getPropsForListItem();
},
render() {
if (this.childView) {
this.childView.remove();
this.childView = null;
}
const props = this.getProps();
this.childView = new Whisper.ReactWrapperView({
className: 'list-item-wrapper',
Component: Signal.Components.ConversationListItem,
props,
});
const update = () => this.childView.update(this.getProps());
this.listenTo(this.model, 'change', update);
this.$el.append(this.childView.el);
return this;
},
});
// list of conversations, showing user/group and last message sent
Whisper.ConversationContactListItemView = Whisper.ConversationListItemView.extend(
{
getProps() {
// We don't want to show a timestamp or a message
const props = this.model.getPropsForListItem();
delete props.lastMessage;
delete props.lastUpdated;
delete props.isSelected;
return props;
},
}
);
})();

@ -1,71 +0,0 @@
/* global Whisper, getInboxCollection, $ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ConversationListView = Whisper.ListView.extend({
tagName: 'div',
itemView: Whisper.ConversationListItemView,
getCollection() {
return getInboxCollection();
},
updateLocation(conversation) {
const $el = this.$(`.${conversation.cid}`);
if (!$el || !$el.length) {
window.log.warn(
'updateLocation: did not find element for conversation',
conversation.idForLogging()
);
return;
}
if ($el.length > 1) {
window.log.warn(
'updateLocation: found more than one element for conversation',
conversation.idForLogging()
);
return;
}
const $allConversations = this.$('.conversation-list-item');
const inboxCollection = this.getCollection();
const index = inboxCollection.indexOf(conversation);
const elIndex = $allConversations.index($el);
if (elIndex < 0) {
window.log.warn(
'updateLocation: did not find index for conversation',
conversation.idForLogging()
);
}
if (index === elIndex) {
return;
}
if (index === 0) {
this.$el.prepend($el);
} else if (index === this.collection.length - 1) {
this.$el.append($el);
} else {
const targetConversation = inboxCollection.at(index - 1);
const target = this.$(`.${targetConversation.cid}`);
$el.insertAfter(target);
}
if ($('.selected').length) {
$('.selected')[0].scrollIntoView({
block: 'nearest',
});
}
},
removeItem(conversation) {
const $el = this.$(`.${conversation.cid}`);
if ($el && $el.length > 0) {
$el.remove();
}
},
});
})();

@ -1,177 +0,0 @@
/* global ConversationController, i18n, textsecure, Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
const isSearchable = conversation => conversation.isSearchable();
Whisper.NewContactView = Whisper.View.extend({
templateName: 'new-contact',
className: 'conversation-list-item contact',
events: {
click: 'validate',
},
initialize() {
this.listenTo(this.model, 'change', this.render); // auto update
},
render_attributes() {
// Show the appropriate message based on model validity
const message =
this.model && this.model.isValid()
? i18n('startConversation')
: i18n('invalidNumberError');
return {
number: message,
title: this.model.getNumber(),
avatar: this.model.getAvatar(),
};
},
validate() {
if (this.model.isValid()) {
this.$el.addClass('valid');
} else {
this.$el.removeClass('valid');
}
},
});
Whisper.ConversationSearchView = Whisper.View.extend({
className: 'conversation-search',
initialize(options) {
this.$input = options.input;
this.$new_contact = this.$('.new-contact');
this.typeahead = new Whisper.ConversationCollection();
this.collection = new Whisper.ConversationCollection([], {
comparator(m) {
return m.getTitle().toLowerCase();
},
});
this.listenTo(this.collection, 'select', conversation => {
this.resetTypeahead();
this.trigger('open', conversation);
});
// View to display the matched contacts from typeahead
this.typeahead_view = new Whisper.ConversationListView({
collection: this.collection,
});
this.$el.append(this.typeahead_view.el);
this.initNewContact();
this.pending = Promise.resolve();
},
events: {
'click .new-contact': 'createConversation',
},
filterContacts() {
const query = this.$input.val().trim();
if (query.length) {
// Update the contact model
this.new_contact_view.model.set('id', query);
this.new_contact_view.render().$el.hide();
this.new_contact_view.validate();
this.hideHints();
// NOTE: Temporarily allow `then` until we convert the entire file
// to `async` / `await`:
/* eslint-disable more/no-then */
this.pending = this.pending.then(() =>
this.typeahead.search(query).then(() => {
let results = this.typeahead.filter(isSearchable);
const noteToSelf = i18n('noteToSelf');
if (noteToSelf.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
const ourNumber = textsecure.storage.user.getNumber();
const conversation = ConversationController.get(ourNumber);
if (conversation) {
// ensure that we don't have duplicates in our results
results = results.filter(item => item.id !== ourNumber);
results.unshift(conversation);
}
}
this.typeahead_view.collection.reset(results);
// This will allow us to show the last message when searching
this.typeahead_view.collection.forEach(c => c.updateLastMessage());
// Show the new contact view if we already have results
if (this.typeahead_view.collection.length === 0) {
this.new_contact_view.$el.show();
}
})
);
/* eslint-enable more/no-then */
this.trigger('show');
} else {
this.resetTypeahead();
}
},
initNewContact() {
if (this.new_contact_view) {
this.new_contact_view.undelegateEvents();
this.new_contact_view.$el.hide();
}
const model = new Whisper.Conversation({ type: 'private' });
this.new_contact_view = new Whisper.NewContactView({
el: this.$new_contact,
model,
}).render();
},
async createConversation() {
const isValidNumber = this.new_contact_view.model.isValid();
if (!isValidNumber) {
this.$input.focus();
return;
}
const newConversationId = this.new_contact_view.model.id;
const conversation = await ConversationController.getOrCreateAndWait(
newConversationId,
'private'
);
this.trigger('open', conversation);
this.initNewContact();
this.resetTypeahead();
},
reset() {
this.delegateEvents();
this.typeahead_view.delegateEvents();
this.new_contact_view.delegateEvents();
this.resetTypeahead();
},
resetTypeahead() {
this.hideHints();
this.new_contact_view.$el.hide();
this.$input.val('').focus();
this.typeahead_view.collection.reset([]);
this.trigger('hide');
},
showHints() {
if (!this.hintView) {
this.hintView = new Whisper.HintView({
className: 'contact placeholder',
content: i18n('newPhoneNumber'),
}).render();
this.hintView.$el.insertAfter(this.$input);
}
this.hintView.$el.show();
},
hideHints() {
if (this.hintView) {
this.hintView.remove();
this.hintView = null;
}
},
});
})();

@ -172,6 +172,7 @@
isClosable: this.model.isClosable(),
isBlocked: this.model.isBlocked(),
isGroup: !this.model.isPrivate(),
isPrivate: this.model.isPrivate(),
isOnline: this.model.isOnline(),
isArchived: this.model.get('isArchived'),
isPublic: this.model.isPublic(),

@ -1,7 +1,6 @@
/* global window: false */
/* global callWorker: false */
/* global textsecure: false */
/* global libsignal: false */
/* global WebSocket: false */
/* global Event: false */
/* global dcodeIO: false */
@ -18,14 +17,8 @@ function MessageReceiver(username, password, signalingKey) {
this.count = 0;
this.signalingKey = signalingKey;
this.username = username;
this.password = password;
this.server = WebAPI.connect();
const address = libsignal.SignalProtocolAddress.fromString(username);
this.number = address.getName();
this.deviceId = address.getDeviceId();
this.pending = Promise.resolve();
// only do this once to prevent duplicates

@ -453,6 +453,26 @@ MessageSender.prototype = {
return libsession.getMessageQueue().sendSyncMessage(openGroupsSyncMessage);
},
async sendBlockedListSyncMessage() {
// If we havn't got a primaryDeviceKey then we are in the middle of pairing
// primaryDevicePubKey is set to our own number if we are the master device
const primaryDeviceKey = window.storage.get('primaryDevicePubKey');
if (!primaryDeviceKey) {
return Promise.resolve();
}
const currentlyBlockedNumbers = window.BlockedNumberController.getBlockedNumbers();
// currently we only sync user blocked, not groups
const blockedSyncMessage = new libsession.Messages.Outgoing.BlockedListSyncMessage(
{
timestamp: Date.now(),
numbers: currentlyBlockedNumbers,
groups: [],
}
);
return libsession.getMessageQueue().sendSyncMessage(blockedSyncMessage);
},
syncReadMessages(reads) {
const myDevice = textsecure.storage.user.getDeviceId();
// FIXME currently not in used
@ -509,8 +529,8 @@ MessageSender.prototype = {
window.textsecure = window.textsecure || {};
textsecure.MessageSender = function MessageSenderWrapper(username, password) {
const sender = new MessageSender(username, password);
textsecure.MessageSender = function MessageSenderWrapper() {
const sender = new MessageSender();
this.sendContactSyncMessage = sender.sendContactSyncMessage.bind(sender);
this.sendGroupSyncMessage = sender.sendGroupSyncMessage.bind(sender);
this.sendOpenGroupsSyncMessage = sender.sendOpenGroupsSyncMessage.bind(
@ -521,6 +541,9 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
this.syncVerification = sender.syncVerification.bind(sender);
this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender);
this.getProxiedSize = sender.getProxiedSize.bind(sender);
this.sendBlockedListSyncMessage = sender.sendBlockedListSyncMessage.bind(
sender
);
};
textsecure.MessageSender.prototype = {

@ -95,8 +95,6 @@ describe('MessageReceiver', () => {
});
window.messageReceiver = new textsecure.MessageReceiver(
'username',
'password',
'signalingKey'
// 'ws://localhost:8080',
// window,

@ -3,7 +3,7 @@
'use strict';
describe('ConversationCollection', () => {
textsecure.messaging = new textsecure.MessageSender('');
textsecure.messaging = new textsecure.MessageSender();
before(clearDatabase);
after(clearDatabase);

@ -1,67 +0,0 @@
/* global $, Whisper */
describe('ConversationSearchView', () => {
describe('Searching for left groups', () => {
let convo;
before(() => {
convo = new Whisper.ConversationCollection().add({
id: '1-search-view',
name: 'i left this group',
members: [],
type: 'group',
left: true,
});
return window.Signal.Data.saveConversation(convo.attributes, {
Conversation: Whisper.Conversation,
});
});
describe('with no messages', () => {
let input;
let view;
before(done => {
input = $('<input>');
view = new Whisper.ConversationSearchView({ input }).render();
view.$input.val('left');
view.filterContacts();
view.typeahead_view.collection.on('reset', () => {
done();
});
});
it('should not surface left groups with no messages', () => {
assert.isUndefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
});
describe('with messages', () => {
let input;
let view;
before(async () => {
input = $('<input>');
view = new Whisper.ConversationSearchView({ input }).render();
convo.set({ id: '2-search-view', left: false });
await window.Signal.Data.saveConversation(convo.attributes, {
Conversation: Whisper.Conversation,
});
view.$input.val('left');
view.filterContacts();
return new Promise(resolve => {
view.typeahead_view.collection.on('reset', resolve);
});
});
it('should surface left groups with messages', () => {
assert.isDefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
});
});
});

@ -11,6 +11,14 @@ import { ContactName } from './conversation/ContactName';
import { TypingAnimation } from './conversation/TypingAnimation';
import { Colors, LocalizerType } from '../types/Util';
import {
getBlockMenuItem,
getClearNicknameMenuItem,
getCopyIdMenuItem,
getDeleteContactMenuItem,
getInviteContactMenuItem,
getLeaveGroupMenuItem,
} from '../session/utils/Menu';
export type PropsData = {
id: string;
@ -43,6 +51,7 @@ export type PropsData = {
hasNickname?: boolean;
isSecondary?: boolean;
isGroupInvitation?: boolean;
isKickedFromGroup?: boolean;
};
type PropsHousekeeping = {
@ -56,6 +65,7 @@ type PropsHousekeeping = {
onClearNickname?: () => void;
onCopyPublicKey?: () => void;
onUnblockContact?: () => void;
onInviteContacts?: () => void;
};
type Props = PropsData & PropsHousekeeping;
@ -163,46 +173,73 @@ export class ConversationListItem extends React.PureComponent<Props> {
isRss,
isPublic,
hasNickname,
type,
isKickedFromGroup,
onDeleteContact,
onDeleteMessages,
onBlockContact,
onChangeNickname,
onClearNickname,
onCopyPublicKey,
onUnblockContact,
onInviteContacts,
} = this.props;
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? onUnblockContact : onBlockContact;
const isPrivate = type === 'direct';
return (
<ContextMenu id={triggerId}>
{!isPublic && !isRss && !isMe ? (
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
) : null}
{getBlockMenuItem(
isMe,
isPrivate,
isBlocked,
onBlockContact,
onUnblockContact,
i18n
)}
{/* {!isPublic && !isRss && !isMe ? (
<MenuItem onClick={onChangeNickname}>
{i18n('changeNickname')}
</MenuItem>
) : null} */}
{!isPublic && !isRss && !isMe && hasNickname ? (
<MenuItem onClick={onClearNickname}>{i18n('clearNickname')}</MenuItem>
) : null}
{!isPublic && !isRss ? (
<MenuItem onClick={onCopyPublicKey}>{i18n('copyPublicKey')}</MenuItem>
) : null}
{getClearNicknameMenuItem(
isPublic,
isRss,
isMe,
hasNickname,
onClearNickname,
i18n
)}
{getCopyIdMenuItem(
isPublic,
isRss,
type === 'group',
onCopyPublicKey,
i18n
)}
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
{!isMe && isClosable ? (
!isPublic ? (
<MenuItem onClick={onDeleteContact}>
{i18n('deleteContact')}
</MenuItem>
) : (
<MenuItem onClick={onDeleteContact}>
{i18n('deletePublicChannel')}
</MenuItem>
)
) : null}
{getInviteContactMenuItem(
type === 'group',
isPublic,
onInviteContacts,
i18n
)}
{getDeleteContactMenuItem(
isMe,
isClosable,
type === 'group',
isPublic,
isRss,
onDeleteContact,
i18n
)}
{getLeaveGroupMenuItem(
isKickedFromGroup,
type === 'group',
isPublic,
isRss,
onDeleteContact,
i18n
)}
</ContextMenu>
);
}

@ -20,6 +20,7 @@ import {
SessionButtonColor,
SessionButtonType,
} from '../session/SessionButton';
import * as Menu from '../../session/utils/Menu';
export interface TimerOption {
name: string;
@ -38,6 +39,7 @@ interface Props {
isMe: boolean;
isClosable?: boolean;
isGroup: boolean;
isPrivate: boolean;
isArchived: boolean;
isPublic: boolean;
isRss: boolean;
@ -304,50 +306,59 @@ export class ConversationHeader extends React.Component<Props> {
onUpdateGroupName,
} = this.props;
const isPrivateGroup = isGroup && !isPublic && !isRss;
const copyIdLabel = isGroup ? i18n('copyChatId') : i18n('copyPublicKey');
return (
<ContextMenu id={triggerId}>
{this.renderPublicMenuItems()}
{!isRss ? (
<MenuItem onClick={onCopyPublicKey}>{copyIdLabel}</MenuItem>
) : null}
{Menu.getCopyIdMenuItem(
isPublic,
isRss,
isGroup,
onCopyPublicKey,
i18n
)}
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
{amMod && !isKickedFromGroup ? (
<MenuItem onClick={onAddModerators}>{i18n('addModerators')}</MenuItem>
) : null}
{amMod && !isKickedFromGroup ? (
<MenuItem onClick={onRemoveModerators}>
{i18n('removeModerators')}
</MenuItem>
) : null}
{amMod && !isKickedFromGroup ? (
<MenuItem onClick={onUpdateGroupName}>
{i18n('editGroupNameOrPicture')}
</MenuItem>
) : null}
{isPrivateGroup && !isKickedFromGroup ? (
<MenuItem onClick={onLeaveGroup}>{i18n('leaveGroup')}</MenuItem>
) : null}
{Menu.getAddModeratorsMenuItem(
amMod,
isKickedFromGroup,
onAddModerators,
i18n
)}
{Menu.getRemoveModeratorsMenuItem(
amMod,
isKickedFromGroup,
onRemoveModerators,
i18n
)}
{Menu.getUpdateGroupNameMenuItem(
amMod,
isKickedFromGroup,
onUpdateGroupName,
i18n
)}
{Menu.getLeaveGroupMenuItem(
isKickedFromGroup,
isGroup,
isPublic,
isRss,
onLeaveGroup,
i18n
)}
{/* TODO: add delete group */}
{isGroup && isPublic ? (
<MenuItem onClick={onInviteContacts}>
{i18n('inviteContacts')}
</MenuItem>
) : null}
{!isMe && isClosable && !isPrivateGroup ? (
!isPublic ? (
<MenuItem onClick={onDeleteContact}>
{i18n('deleteContact')}
</MenuItem>
) : (
<MenuItem onClick={onDeleteContact}>
{i18n('deletePublicChannel')}
</MenuItem>
)
) : null}
{Menu.getInviteContactMenuItem(
isGroup,
isPublic,
onInviteContacts,
i18n
)}
{Menu.getDeleteContactMenuItem(
isMe,
isClosable,
isGroup,
isPublic,
isRss,
onDeleteContact,
i18n
)}
</ContextMenu>
);
}
@ -433,6 +444,7 @@ export class ConversationHeader extends React.Component<Props> {
isBlocked,
isMe,
isGroup,
isPrivate,
isKickedFromGroup,
isPublic,
isRss,
@ -445,43 +457,45 @@ export class ConversationHeader extends React.Component<Props> {
onUnblockUser,
} = this.props;
if (isPublic || isRss) {
return null;
}
const disappearingTitle = i18n('disappearingMessages') as any;
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? onUnblockUser : onBlockUser;
const disappearingMessagesMenuItem = !isKickedFromGroup && !isBlocked && (
<SubMenu title={disappearingTitle}>
{(timerOptions || []).map(item => (
<MenuItem
key={item.value}
onClick={() => {
onSetDisappearingMessages(item.value);
}}
>
{item.name}
</MenuItem>
))}
</SubMenu>
const disappearingMessagesMenuItem = Menu.getDisappearingMenuItem(
isPublic,
isRss,
isKickedFromGroup,
isBlocked,
timerOptions,
onSetDisappearingMessages,
i18n
);
const showMembersMenuItem = isGroup && (
<MenuItem onClick={onShowGroupMembers}>{i18n('showMembers')}</MenuItem>
const showMembersMenuItem = Menu.getShowMemberMenuItem(
isPublic,
isRss,
isGroup,
onShowGroupMembers,
i18n
);
const showSafetyNumberMenuItem = !isGroup && !isMe && (
<MenuItem onClick={onShowSafetyNumber}>
{i18n('showSafetyNumber')}
</MenuItem>
const showSafetyNumberMenuItem = Menu.getShowSafetyNumberMenuItem(
isPublic,
isRss,
isGroup,
isMe,
onShowSafetyNumber,
i18n
);
const resetSessionMenuItem = !isGroup && (
<MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
const resetSessionMenuItem = Menu.getResetSessionMenuItem(
isPublic,
isRss,
isGroup,
onResetSession,
i18n
);
const blockHandlerMenuItem = !isMe && !isRss && (
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
const blockHandlerMenuItem = Menu.getBlockMenuItem(
isMe,
isPrivate,
isBlocked,
onBlockUser,
onUnblockUser,
i18n
);
return (

@ -17,6 +17,8 @@ import { handleContacts } from './multidevice';
import { onGroupReceived } from './groups';
import { MultiDeviceProtocol } from '../session/protocols';
import { DataMessage } from '../session/messages/outgoing';
import { BlockedNumberController } from '../util';
import { StringUtils } from '../session/utils';
export async function handleSyncMessage(
envelope: EnvelopePlus,
@ -36,6 +38,14 @@ export async function handleSyncMessage(
);
}
// remove empty fields (generated by ts even if they should be null)
if (syncMessage.openGroups && !syncMessage.openGroups.length) {
syncMessage.openGroups = null;
}
if (syncMessage.read && !syncMessage.read.length) {
syncMessage.read = null;
}
if (syncMessage.sent) {
const sentMessage = syncMessage.sent;
const message = sentMessage.message as SignalService.IDataMessage;
@ -166,16 +176,42 @@ async function handleBlocked(
blocked: SignalService.SyncMessage.IBlocked
) {
window.log.info('Setting these numbers as blocked:', blocked.numbers);
window.textsecure.storage.put('blocked', blocked.numbers);
const groupIds = _.map(blocked.groupIds, (groupId: any) =>
groupId.toBinary()
);
window.log.info(
'Setting these groups as blocked:',
groupIds.map((groupId: any) => `group(${groupId})`)
);
window.textsecure.storage.put('blocked-groups', groupIds);
// blocked.numbers contains numbers
if (blocked.numbers) {
const currentlyBlockedNumbers = BlockedNumberController.getBlockedNumbers();
const toRemoveFromBlocked = _.difference(
currentlyBlockedNumbers,
blocked.numbers
);
const toAddToBlocked = _.difference(
blocked.numbers,
currentlyBlockedNumbers
);
async function markConvoBlocked(block: boolean, n: string) {
const conv = await window.ConversationController.get(n);
if (conv) {
if (conv.isPrivate()) {
await BlockedNumberController.setBlocked(n, block);
} else {
window.console.warn('Ignoring block/unblock for group:', n);
}
conv.trigger('change', conv);
} else {
window.console.warn(
'Did not find corresponding conversation to block',
n
);
}
}
await Promise.all(toAddToBlocked.map(async n => markConvoBlocked(true, n)));
await Promise.all(
toRemoveFromBlocked.map(async n => markConvoBlocked(false, n))
);
}
await removeFromCache(envelope);
}

@ -0,0 +1,43 @@
import { SyncMessage } from './SyncMessage';
import { SignalService } from '../../../../../protobuf';
import { MessageParams } from '../../Message';
import { StringUtils } from '../../../../utils';
interface BlockedListSyncMessageParams extends MessageParams {
groups: Array<string>;
numbers: Array<string>;
}
export abstract class BlockedListSyncMessage extends SyncMessage {
public readonly groups: Array<Uint8Array>;
public readonly numbers: Array<string>;
constructor(params: BlockedListSyncMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
this.groups = params.groups.map(g => {
if (typeof g !== 'string') {
throw new TypeError(
`invalid group id (expected string) found:${typeof g}`
);
}
return new Uint8Array(StringUtils.encode(g, 'utf8'));
});
if (params.numbers.length && typeof params.numbers[0] !== 'string') {
throw new TypeError(
`invalid number (expected string) found:${typeof params.numbers[0]}`
);
}
this.numbers = params.numbers;
}
protected syncProto(): SignalService.SyncMessage {
const syncMessage = super.syncProto();
// currently we do not handle the closed group blocked
syncMessage.blocked = new SignalService.SyncMessage.Blocked({
numbers: this.numbers,
groupIds: this.groups,
});
return syncMessage;
}
}

@ -6,3 +6,4 @@ export * from './SyncMessage';
export * from './SentSyncMessage';
export * from './SyncReadMessage';
export * from './VerifiedSyncMessage';
export * from './BlockedListSyncMessage';

@ -0,0 +1,336 @@
import React from 'react';
import { MenuItem, SubMenu } from 'react-contextmenu';
import { LocalizerType } from '../../types/Util';
import { TimerOption } from '../../components/conversation/ConversationHeader';
function showTimerOptions(
isPublic: boolean,
isRss: boolean,
isKickedFromGroup: boolean,
isBlocked: boolean
): boolean {
return (
Boolean(!isPublic) && Boolean(!isRss) && !isKickedFromGroup && !isBlocked
);
}
function showMemberMenu(
isPublic: boolean,
isRss: boolean,
isGroup: boolean
): boolean {
return Boolean(!isPublic) && Boolean(!isRss) && isGroup;
}
function showSafetyNumber(
isPublic: boolean,
isRss: boolean,
isGroup: boolean,
isMe: boolean
): boolean {
return Boolean(!isPublic) && Boolean(!isRss) && !isGroup && !isMe;
}
function showResetSession(
isPublic: boolean,
isRss: boolean,
isGroup: boolean
): boolean {
return Boolean(!isPublic) && Boolean(!isRss) && Boolean(!isGroup);
}
function showBlock(
isMe: boolean | undefined,
isPrivate: boolean | undefined
): boolean {
return Boolean(!isMe) && Boolean(isPrivate);
}
function showClearNickname(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isMe: boolean | undefined,
hasNickname: boolean | undefined
): boolean {
return (
Boolean(!isPublic) &&
Boolean(!isRss) &&
Boolean(!isMe) &&
Boolean(hasNickname)
);
}
function showCopyId(
isPublic: boolean | undefined,
isRss: boolean | undefined
): boolean {
return Boolean(!isPublic) && Boolean(!isRss);
}
function showDeleteContact(
isMe: boolean | undefined,
isClosable: boolean | undefined,
isGroup: boolean | undefined,
isPublic: boolean | undefined,
isRss: boolean | undefined
): boolean {
return (
Boolean(!isMe) && Boolean(isClosable) && !!(!isGroup || isPublic || isRss)
);
}
function showAddModerators(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined
): boolean {
return Boolean(!isKickedFromGroup) && Boolean(amMod);
}
function showRemoveModerators(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined
): boolean {
return Boolean(!isKickedFromGroup) && Boolean(amMod);
}
function showUpdateGroupName(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined
): boolean {
return Boolean(!isKickedFromGroup) && Boolean(amMod);
}
function showLeaveGroup(
isKickedFromGroup: boolean | undefined,
isGroup: boolean | undefined,
isPublic: boolean | undefined,
isRss: boolean | undefined
): boolean {
return Boolean(!isKickedFromGroup) && Boolean(isGroup) && !isPublic && !isRss;
}
function showInviteContact(
isGroup: boolean | undefined,
isPublic: boolean | undefined
): boolean {
return Boolean(isGroup) && Boolean(isPublic);
}
/** Menu items standardized */
export function getInviteContactMenuItem(
isGroup: boolean | undefined,
isPublic: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showInviteContact(isGroup, isPublic)) {
return <MenuItem onClick={action}>{i18n('inviteContacts')}</MenuItem>;
}
return null;
}
export function getDeleteContactMenuItem(
isMe: boolean | undefined,
isClosable: boolean | undefined,
isGroup: boolean | undefined,
isPublic: boolean | undefined,
isRss: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showDeleteContact(isMe, isClosable, isGroup, isPublic, isRss)) {
if (isPublic) {
return (
<MenuItem onClick={action}>{i18n('deletePublicChannel')}</MenuItem>
);
}
return <MenuItem onClick={action}>{i18n('deleteContact')}</MenuItem>;
}
return null;
}
export function getLeaveGroupMenuItem(
isKickedFromGroup: boolean | undefined,
isGroup: boolean | undefined,
isPublic: boolean | undefined,
isRss: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showLeaveGroup(isKickedFromGroup, isGroup, isPublic, isRss)) {
return <MenuItem onClick={action}>{i18n('leaveGroup')}</MenuItem>;
}
return null;
}
export function getUpdateGroupNameMenuItem(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showUpdateGroupName(amMod, isKickedFromGroup)) {
return (
<MenuItem onClick={action}>{i18n('editGroupNameOrPicture')}</MenuItem>
);
}
return null;
}
export function getRemoveModeratorsMenuItem(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showRemoveModerators(amMod, isKickedFromGroup)) {
return <MenuItem onClick={action}>{i18n('removeModerators')}</MenuItem>;
}
return null;
}
export function getAddModeratorsMenuItem(
amMod: boolean | undefined,
isKickedFromGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showAddModerators(amMod, isKickedFromGroup)) {
return <MenuItem onClick={action}>{i18n('addModerators')}</MenuItem>;
}
return null;
}
export function getCopyIdMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showCopyId(isPublic, isRss)) {
const copyIdLabel = isGroup ? i18n('copyChatId') : i18n('copyPublicKey');
return <MenuItem onClick={action}>{copyIdLabel}</MenuItem>;
}
return null;
}
export function getDisappearingMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isKickedFromGroup: boolean | undefined,
isBlocked: boolean | undefined,
timerOptions: Array<TimerOption>,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (
showTimerOptions(
Boolean(isPublic),
Boolean(isRss),
Boolean(isKickedFromGroup),
Boolean(isBlocked)
)
) {
return (
<SubMenu title={i18n('disappearingMessages') as any}>
{(timerOptions || []).map(item => (
<MenuItem
key={item.value}
onClick={() => {
action(item.value);
}}
>
{item.name}
</MenuItem>
))}
</SubMenu>
);
}
return null;
}
export function getShowMemberMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showMemberMenu(Boolean(isPublic), Boolean(isRss), Boolean(isGroup))) {
return <MenuItem onClick={action}>{i18n('showMembers')}</MenuItem>;
}
return null;
}
export function getShowSafetyNumberMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isGroup: boolean | undefined,
isMe: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (
showSafetyNumber(
Boolean(isPublic),
Boolean(isRss),
Boolean(isGroup),
Boolean(isMe)
)
) {
return <MenuItem onClick={action}>{i18n('showSafetyNumber')}</MenuItem>;
}
return null;
}
export function getResetSessionMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isGroup: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (showResetSession(Boolean(isPublic), Boolean(isRss), Boolean(isGroup))) {
return <MenuItem onClick={action}>{i18n('resetSession')}</MenuItem>;
}
return null;
}
export function getBlockMenuItem(
isMe: boolean | undefined,
isPrivate: boolean | undefined,
isBlocked: boolean | undefined,
actionBlock: any,
actionUnblock: any,
i18n: LocalizerType
): JSX.Element | null {
if (showBlock(Boolean(isMe), Boolean(isPrivate))) {
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? actionUnblock : actionBlock;
return <MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>;
}
return null;
}
export function getClearNicknameMenuItem(
isPublic: boolean | undefined,
isRss: boolean | undefined,
isMe: boolean | undefined,
hasNickname: boolean | undefined,
action: any,
i18n: LocalizerType
): JSX.Element | null {
if (
showClearNickname(
Boolean(isPublic),
Boolean(isRss),
Boolean(isMe),
Boolean(hasNickname)
)
) {
return <MenuItem onClick={action}>{i18n('clearNickname')}</MenuItem>;
}
return null;
}

@ -52,7 +52,7 @@ export async function poll(
): Promise<void> {
const defaults: PollOptions = {
timeout: 2000,
interval: 1000,
interval: 100,
};
const { timeout, interval } = {
@ -113,6 +113,7 @@ export async function waitUntil(
},
{
timeout,
interval: timeout / 20,
}
);
}

@ -5,6 +5,7 @@ import * as StringUtils from './String';
import * as NumberUtils from './Number';
import * as PromiseUtils from './Promise';
import * as ProtobufUtils from './Protobuf';
import * as MenuUtils from './Menu';
export * from './Attachments';
export * from './TypedEmitter';
@ -18,4 +19,5 @@ export {
NumberUtils,
PromiseUtils,
ProtobufUtils,
MenuUtils,
};

Loading…
Cancel
Save