From 8cc9b7b54b2438134eaa1b300ec8c22f2a2789f8 Mon Sep 17 00:00:00 2001
From: Mikunj
Date: Fri, 16 Nov 2018 13:29:31 +1100
Subject: [PATCH 01/12] Added blocked number model and collection. Added
blocked number controller. Hooked up BlockedNumberController loading.
---
background.html | 1 +
js/background.js | 2 +
js/blocked_number_controller.js | 72 +++++++++++++++++++++++++++++++++
js/models/blockedNumbers.js | 38 ++++++++++++++++-
js/signal_protocol_store.js | 4 +-
test/index.html | 1 +
6 files changed, 116 insertions(+), 2 deletions(-)
create mode 100644 js/blocked_number_controller.js
diff --git a/background.html b/background.html
index d17fcf7e4..a37e90fe1 100644
--- a/background.html
+++ b/background.html
@@ -614,6 +614,7 @@
+
diff --git a/js/background.js b/js/background.js
index 1c1afbc9d..b5a1df155 100644
--- a/js/background.js
+++ b/js/background.js
@@ -9,6 +9,7 @@
textsecure,
WebAPI
Whisper,
+ BlockedNumberController
*/
// eslint-disable-next-line func-names
@@ -418,6 +419,7 @@
try {
await ConversationController.load();
+ BlockedNumberController.load();
} catch (error) {
window.log.error(
'background.js: ConversationController failed to load:',
diff --git a/js/blocked_number_controller.js b/js/blocked_number_controller.js
new file mode 100644
index 000000000..24cfedd76
--- /dev/null
+++ b/js/blocked_number_controller.js
@@ -0,0 +1,72 @@
+/* global , Whisper, storage */
+/* global textsecure: false */
+
+/* eslint-disable more/no-then */
+
+// eslint-disable-next-line func-names
+(function() {
+ 'use strict';
+
+ window.Whisper = window.Whisper || {};
+
+ const blockedNumbers = new Whisper.BlockedNumberCollection();
+ window.getBlockedNumbers = () => blockedNumbers;
+
+ window.BlockedNumberController = {
+ reset() {
+ blockedNumbers.reset([]);
+ },
+ load() {
+ window.log.info('BlockedNumberController: starting initial fetch');
+
+ if (blockedNumbers.length) {
+ throw new Error('BlockedNumberController: Already loaded!');
+ }
+
+ if (!storage) {
+ throw new Error('BlockedNumberController: Could not load blocked numbers');
+ }
+
+ // Add the numbers to the collection
+ const numbers = storage.getBlockedNumbers();
+ blockedNumbers.add(
+ numbers.map(number => ({ number }))
+ );
+ },
+ block(number) {
+ const ourNumber = textsecure.storage.user.getNumber();
+
+ // Make sure we don't block ourselves
+ if (ourNumber === number) {
+ window.log.info('BlockedNumberController: Cannot block yourself!');
+ return null;
+ }
+
+ storage.addBlockedNumber(number);
+
+ // Make sure we don't add duplicates
+ const exists = blockedNumbers.getNumber(number);
+ if (exists)
+ return exists;
+
+ return blockedNumbers.add({ number });
+ },
+ unblock(number) {
+ storage.removeBlockedNumber(number);
+
+ // Make sure we don't add duplicates
+ const exists = blockedNumbers.getNumber(number);
+ if (exists) {
+ blockedNumbers.remove(exists);
+ return exists;
+ }
+
+ return null;
+ },
+ isBlocked(number) {
+ return storage.isBlocked(number);
+ },
+
+ };
+ })();
+
\ No newline at end of file
diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js
index 10ef67f0f..7e1f79914 100644
--- a/js/models/blockedNumbers.js
+++ b/js/models/blockedNumbers.js
@@ -1,9 +1,19 @@
/* global storage, _ */
+/* global _: false */
+/* global Backbone: false */
+
+/* global BlockedNumberController: false */
+/* global storage: false */
+/* global Whisper: false */
+
+/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
+ window.Whisper = window.Whisper || {};
+
const BLOCKED_NUMBERS_ID = 'blocked';
const BLOCKED_GROUPS_ID = 'blocked-groups';
@@ -12,6 +22,7 @@
return _.include(numbers, number);
};
+ storage.getBlockedNumbers = () => storage.get(BLOCKED_NUMBERS_ID, []);
storage.addBlockedNumber = number => {
const numbers = storage.get(BLOCKED_NUMBERS_ID, []);
if (_.include(numbers, number)) {
@@ -54,4 +65,29 @@
window.log.info(`removing group(${groupId} from blocked list`);
storage.put(BLOCKED_GROUPS_ID, _.without(groupIds, groupId));
};
-})();
+
+ Whisper.BlockedNumber = Backbone.Model.extend({
+ defaults() {
+ return {
+ number: '',
+ };
+ },
+ block() {
+ return BlockedNumberController.block(this.number);
+ },
+ unblock() {
+ return BlockedNumberController.unblock(this.number);
+ },
+ });
+
+ Whisper.BlockedNumberCollection = Backbone.Collection.extend({
+ model: Whisper.BlockedNumber,
+ comparator(m) {
+ return m.get('number');
+ },
+ getNumber(number) {
+ return this.model.find(m => m.number === number);
+ },
+ });
+
+})();
\ No newline at end of file
diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js
index 760f9c99e..80185d238 100644
--- a/js/signal_protocol_store.js
+++ b/js/signal_protocol_store.js
@@ -1,5 +1,5 @@
/* global
- dcodeIO, Backbone, _, libsignal, textsecure, ConversationController, stringObject */
+ dcodeIO, Backbone, _, libsignal, textsecure, ConversationController, stringObject, BlockedNumberController */
/* eslint-disable no-proto */
@@ -955,7 +955,9 @@
await window.storage.fetch();
ConversationController.reset();
+ BlockedNumberController.reset();
await ConversationController.load();
+ BlockedNumberController.load();
},
async removeAllConfiguration() {
await window.Signal.Data.removeAllConfiguration();
diff --git a/test/index.html b/test/index.html
index 64964f896..e03ce96cf 100644
--- a/test/index.html
+++ b/test/index.html
@@ -345,6 +345,7 @@
+
From ae2e2fa2ae1f46d977287130932cb4d6e6916354 Mon Sep 17 00:00:00 2001
From: Mikunj
Date: Fri, 16 Nov 2018 14:42:42 +1100
Subject: [PATCH 02/12] Updated UI in conversation to support user blocking
behaviour.
---
_locales/en/messages.json | 6 ++++++
js/models/blockedNumbers.js | 2 +-
js/models/conversations.js | 15 +++++++++++++--
js/views/conversation_view.js | 9 +++++++++
stylesheets/_modules.scss | 5 +++++
stylesheets/_theme_dark.scss | 4 ++++
ts/components/ConversationListItem.tsx | 6 ++++--
ts/components/conversation/ConversationHeader.tsx | 14 ++++++++++++++
8 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index fe19c3d27..471ffe52c 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1619,5 +1619,11 @@
"example": "Bob"
}
}
+ },
+ "blockUser": {
+ "message": "Block user"
+ },
+ "unblockUser": {
+ "message": "Unblock user"
}
}
diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js
index 7e1f79914..b31d24e58 100644
--- a/js/models/blockedNumbers.js
+++ b/js/models/blockedNumbers.js
@@ -86,7 +86,7 @@
return m.get('number');
},
getNumber(number) {
- return this.model.find(m => m.number === number);
+ return this.models.find(m => m.number === number);
},
});
diff --git a/js/models/conversations.js b/js/models/conversations.js
index 47be7e3e2..d15fea527 100644
--- a/js/models/conversations.js
+++ b/js/models/conversations.js
@@ -1,6 +1,6 @@
/* global _: false */
/* global Backbone: false */
-/* global libphonenumber: false */
+/* global BlockedNumberController: false */
/* global ConversationController: false */
/* global libsignal: false */
@@ -147,7 +147,17 @@
isMe() {
return this.id === this.ourNumber;
},
-
+ isBlocked() {
+ return BlockedNumberController.isBlocked(this.id);
+ },
+ block() {
+ BlockedNumberController.block(this.id);
+ this.trigger('change');
+ },
+ unblock() {
+ BlockedNumberController.unblock(this.id);
+ this.trigger('change');
+ },
async cleanup() {
await window.Signal.Types.Conversation.deleteExternalFiles(
this.attributes,
@@ -280,6 +290,7 @@
unreadCount: this.get('unreadCount') || 0,
isSelected: this.isSelected,
showFriendRequestIndicator: this.pendingFriendRequest,
+ isBlocked: this.isBlocked(),
lastMessage: {
status: this.lastMessageStatus,
text: this.lastMessage,
diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js
index 35099e78b..6dd0cdca6 100644
--- a/js/views/conversation_view.js
+++ b/js/views/conversation_view.js
@@ -7,6 +7,7 @@
/* global Signal: false */
/* global storage: false */
/* global Whisper: false */
+/* global BlockNumberConversation: false */
// eslint-disable-next-line func-names
(function() {
@@ -170,6 +171,7 @@
isVerified: this.model.isVerified(),
isKeysPending: this.model.isKeyExchangeCompleted() === false,
isMe: this.model.isMe(),
+ isBlocked: this.model.isBlocked(),
isGroup: !this.model.isPrivate(),
expirationSettingName,
showBackButton: Boolean(this.panels && this.panels.length),
@@ -200,6 +202,13 @@
this.resetPanel();
this.updateHeader();
},
+
+ onBlockUser: () => {
+ this.model.block();
+ },
+ onUnblockUser: () => {
+ this.model.unblock();
+ },
};
};
this.titleView = new Whisper.ReactWrapperView({
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 5430952bd..d4c1dfe56 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -1831,6 +1831,11 @@
border-left: 4px solid $color-conversation-indigo;
}
+.module-conversation-list-item--is-blocked {
+ padding-left: 12px;
+ border-left: 4px solid $color-conversation-red;
+}
+
.module-conversation-list-item--is-selected {
background-color: $color-gray-05;
}
diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss
index 190c077a9..87dc3f85e 100644
--- a/stylesheets/_theme_dark.scss
+++ b/stylesheets/_theme_dark.scss
@@ -1269,6 +1269,10 @@ body.dark-theme {
border-left: 4px solid $color-conversation-indigo;
}
+ .module-conversation-list-item--is-blocked {
+ border-left: 4px solid $color-conversation-red;
+ }
+
.module-conversation-list-item--is-selected {
background-color: $color-dark-70;
}
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index a2e9cf644..574b626d6 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -24,6 +24,7 @@ interface Props {
text: string;
};
showFriendRequestIndicator?: boolean;
+ isBlocked: boolean;
i18n: Localizer;
onClick?: () => void;
@@ -157,7 +158,7 @@ export class ConversationListItem extends React.Component {
}
public render() {
- const { unreadCount, onClick, isSelected, showFriendRequestIndicator } = this.props;
+ const { unreadCount, onClick, isSelected, showFriendRequestIndicator, isBlocked } = this.props;
return (
{
'module-conversation-list-item',
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
- showFriendRequestIndicator ? 'module-conversation-list-item--has-friend-request' : null
+ showFriendRequestIndicator ? 'module-conversation-list-item--has-friend-request' : null,
+ isBlocked ? 'module-conversation-list-item--is-blocked' : null,
)}
>
{this.renderAvatar()}
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx
index 705973309..49a99a6ed 100644
--- a/ts/components/conversation/ConversationHeader.tsx
+++ b/ts/components/conversation/ConversationHeader.tsx
@@ -30,6 +30,7 @@ interface Props {
color: string;
avatarPath?: string;
+ isBlocked: boolean;
isMe: boolean;
isGroup: boolean;
expirationSettingName?: string;
@@ -44,6 +45,9 @@ interface Props {
onShowAllMedia: () => void;
onShowGroupMembers: () => void;
onGoBack: () => void;
+
+ onBlockUser: () => void;
+ onUnblockUser: () => void;
}
export class ConversationHeader extends React.Component
{
@@ -182,6 +186,7 @@ export class ConversationHeader extends React.Component {
public renderMenu(triggerId: string) {
const {
i18n,
+ isBlocked,
isMe,
isGroup,
onDeleteMessages,
@@ -191,10 +196,15 @@ export class ConversationHeader extends React.Component {
onShowGroupMembers,
onShowSafetyNumber,
timerOptions,
+ onBlockUser,
+ onUnblockUser,
} = this.props;
const disappearingTitle = i18n('disappearingMessages') as any;
+ const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
+ const blockHandler = isBlocked ? onUnblockUser : onBlockUser;
+
return (
@@ -223,6 +233,10 @@ export class ConversationHeader extends React.Component {
{!isGroup ? (
) : null}
+ {/* Only show the block on other conversations */}
+ {!isMe ? (
+
+ ) : null}
);
From a82999818a087584272e67b56c2330576a2906ed Mon Sep 17 00:00:00 2001
From: Mikunj
Date: Fri, 16 Nov 2018 15:28:10 +1100
Subject: [PATCH 03/12] Added blocking/unblocking from friend request. Fixed
incoming messages being deleted.
---
app/sql.js | 4 +++-
js/background.js | 5 +++++
js/blocked_number_controller.js | 8 +++++++-
js/models/messages.js | 14 +++++++++++++-
js/views/settings_view.js | 1 +
settings.html | 4 ++++
ts/components/conversation/FriendRequest.tsx | 8 +++++++-
7 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/app/sql.js b/app/sql.js
index 79d51f3b5..623f487f1 100644
--- a/app/sql.js
+++ b/app/sql.js
@@ -1302,7 +1302,9 @@ async function getMessageBySender({ source, sourceDevice, sent_at }) {
async function getAllUnsentMessages() {
const rows = await db.all(`
- SELECT json FROM messages WHERE NOT sent
+ SELECT json FROM messages WHERE
+ type IN ('outgoing', 'friend-request') AND
+ NOT sent
ORDER BY sent_at DESC;
`);
return map(rows, row => jsonToObject(row.json));
diff --git a/js/background.js b/js/background.js
index b5a1df155..4e8f20433 100644
--- a/js/background.js
+++ b/js/background.js
@@ -338,6 +338,11 @@
'expirationStartTimestamp'
);
+ // Make sure we only target outgoing messages
+ if (message.isFriendRequest() && message.get('direction') === 'incoming') {
+ return;
+ }
+
if (message.hasErrors()) {
return;
}
diff --git a/js/blocked_number_controller.js b/js/blocked_number_controller.js
index 24cfedd76..4e4fac2d0 100644
--- a/js/blocked_number_controller.js
+++ b/js/blocked_number_controller.js
@@ -63,10 +63,16 @@
return null;
},
+ unblockAll() {
+ const all = blockedNumbers.models;
+ all.forEach(number => {
+ storage.removeBlockedNumber(number);
+ blockedNumbers.remove(number);
+ })
+ },
isBlocked(number) {
return storage.isBlocked(number);
},
-
};
})();
\ No newline at end of file
diff --git a/js/models/messages.js b/js/models/messages.js
index f3860f648..56ff9bc40 100644
--- a/js/models/messages.js
+++ b/js/models/messages.js
@@ -331,15 +331,27 @@
window.Whisper.events.trigger('deleteConversation', conversation);
};
+ const onBlockUser = () => {
+ conversation.block();
+ this.trigger('change');
+ };
+
+ const onUnblockUser = () => {
+ conversation.unblock();
+ this.trigger('change');
+ };
+
return {
text: this.createNonBreakingLastSeparator(this.get('body')),
status: this.getMessagePropStatus(),
direction,
friendStatus,
+ isBlocked: conversation.isBlocked(),
onAccept,
onDecline,
onDeleteConversation,
- onRetrySend: () => this.retrySend(),
+ onBlockUser,
+ onUnblockUser,
}
},
findContact(phoneNumber) {
diff --git a/js/views/settings_view.js b/js/views/settings_view.js
index 6d2439071..52675463f 100644
--- a/js/views/settings_view.js
+++ b/js/views/settings_view.js
@@ -150,6 +150,7 @@
mediaPermissionsDescription: i18n('mediaPermissionsDescription'),
spellCheckHeader: i18n('spellCheck'),
spellCheckDescription: i18n('spellCheckDescription'),
+ blockedHeader: 'Blocked Users',
};
},
onClose() {
diff --git a/settings.html b/settings.html
index ebc33d209..24586d0c9 100644
--- a/settings.html
+++ b/settings.html
@@ -109,6 +109,10 @@
{{ clearDataExplanation }}
+
+
+
{{ blockedHeader }}
+
diff --git a/ts/components/conversation/FriendRequest.tsx b/ts/components/conversation/FriendRequest.tsx
index 71f27b251..67975d92a 100644
--- a/ts/components/conversation/FriendRequest.tsx
+++ b/ts/components/conversation/FriendRequest.tsx
@@ -10,10 +10,13 @@ interface Props {
status: string;
friendStatus: 'pending' | 'accepted' | 'declined';
i18n: Localizer;
+ isBlocked: boolean;
onAccept: () => void;
onDecline: () => void;
onDeleteConversation: () => void;
onRetrySend: () => void;
+ onBlockUser: () => void;
+ onUnblockUser: () => void;
}
export class FriendRequest extends React.Component {
@@ -50,7 +53,7 @@ export class FriendRequest extends React.Component {
}
public renderButtons() {
- const { friendStatus, direction, status, onAccept, onDecline, onDeleteConversation, onRetrySend } = this.props;
+ const { i18n, friendStatus, direction, status, onAccept, onDecline, onDeleteConversation, onRetrySend, isBlocked, onBlockUser, onUnblockUser } = this.props;
if (direction === 'incoming') {
if (friendStatus === 'pending') {
@@ -67,6 +70,8 @@ export class FriendRequest extends React.Component {
);
} else if (friendStatus === 'declined') {
+ const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
+ const blockHandler = isBlocked ? onUnblockUser : onBlockUser;
return (
{
)}
>
+
);
}
From 658b2b033208a2f036a0eab5dff281fc75befbd0 Mon Sep 17 00:00:00 2001
From: Mikunj
Date: Mon, 19 Nov 2018 12:22:35 +1100
Subject: [PATCH 04/12] List blocked users in settings.
---
js/blocked_number_controller.js | 8 +++++
js/settings_start.js | 4 ++-
js/views/blocked_number_view.js | 56 +++++++++++++++++++++++++++++++++
js/views/settings_view.js | 4 +++
settings.html | 16 +++++++++-
5 files changed, 86 insertions(+), 2 deletions(-)
create mode 100644 js/views/blocked_number_view.js
diff --git a/js/blocked_number_controller.js b/js/blocked_number_controller.js
index 4e4fac2d0..ab71238c5 100644
--- a/js/blocked_number_controller.js
+++ b/js/blocked_number_controller.js
@@ -13,6 +13,14 @@
window.getBlockedNumbers = () => blockedNumbers;
window.BlockedNumberController = {
+ getAll() {
+ try {
+ this.load();
+ } catch (e) {
+ console.warn(e);
+ }
+ return blockedNumbers;
+ },
reset() {
blockedNumbers.reset([]);
},
diff --git a/js/settings_start.js b/js/settings_start.js
index c81438577..f29d09224 100644
--- a/js/settings_start.js
+++ b/js/settings_start.js
@@ -1,4 +1,4 @@
-/* global $, Whisper */
+/* global $, Whisper, storage */
$(document).on('keyup', e => {
'use strict';
@@ -35,6 +35,8 @@ window.initialRequest = getInitialData();
window.initialRequest.then(data => {
'use strict';
+ storage.fetch();
+
window.initialData = data;
window.view = new Whisper.SettingsView();
window.view.$el.appendTo($body);
diff --git a/js/views/blocked_number_view.js b/js/views/blocked_number_view.js
new file mode 100644
index 000000000..98837cae6
--- /dev/null
+++ b/js/views/blocked_number_view.js
@@ -0,0 +1,56 @@
+/* global BlockedNumberController: false */
+/* global Whisper: false */
+/* global storage: false */
+/* global $: false */
+
+/* eslint-disable no-new */
+
+// eslint-disable-next-line func-names
+(function() {
+ 'use strict';
+
+ window.Whisper = window.Whisper || {};
+
+ Whisper.BlockedNumberView = Whisper.View.extend({
+ templateName: 'blockedUserSettings',
+ className: 'blockedUserSettings',
+ events: {
+ 'click .sync': 'sync',
+ },
+ initialize() {
+ storage.onready(() => {
+ this.collection = BlockedNumberController.getAll();
+ this.listView = new Whisper.BlockedNumberListView({
+ collection: this.collection,
+ });
+
+ this.listView.render();
+ this.$('.content').append(this.listView.el);
+ });
+ },
+ render_attributes() {
+ return {
+ blockedHeader: 'Blocked Users',
+ };
+ },
+ });
+
+
+ Whisper.BlockedNumberListView = Whisper.ListView.extend({
+ tagName: 'div',
+ itemView: Whisper.View.extend({
+ tagName: 'div',
+ templateName: 'blockedNumber',
+ initialize() {
+ this.listenTo(this.model, 'change', this.render);
+ },
+ render_attributes() {
+ const number = (this.model && this.model.get('number')) || '-';
+ return {
+ number,
+ }
+ },
+ }),
+ });
+ })();
+
\ No newline at end of file
diff --git a/js/views/settings_view.js b/js/views/settings_view.js
index 52675463f..5604e8599 100644
--- a/js/views/settings_view.js
+++ b/js/views/settings_view.js
@@ -117,6 +117,10 @@
value: window.initialData.mediaPermissions,
setFn: window.setMediaPermissions,
});
+
+ const blockedNumberView = new Whisper.BlockedNumberView().render();
+ this.$('.blocked-user-setting').append(blockedNumberView.el);
+
if (!window.initialData.isPrimary) {
const syncView = new SyncView().render();
this.$('.sync-setting').append(syncView.el);
diff --git a/settings.html b/settings.html
index 24586d0c9..95f834449 100644
--- a/settings.html
+++ b/settings.html
@@ -34,6 +34,15 @@
+
+
+
+
+
+
+
+