Move BlockedNumberController to ts

This commit is contained in:
Mikunj 2020-07-07 09:13:55 +10:00
parent 26e3eca1a2
commit aa75205bbd
17 changed files with 180 additions and 353 deletions

View File

@ -1000,6 +1000,9 @@
"sendMessageLeftGroup": {
"message": "You left this group"
},
"sendMessageBlockedUser": {
"message": "You have blocked the user"
},
"groupMembers": {
"message": "Group members"
},

View File

@ -463,7 +463,6 @@
<script type='text/javascript' src='js/registration.js'></script>
<script type='text/javascript' src='js/expire.js'></script>
<script type='text/javascript' src='js/conversation_controller.js'></script>
<script type='text/javascript' src='js/blocked_number_controller.js'></script>
<script type='text/javascript' src='js/message_controller.js'></script>
<script type='text/javascript' src='js/views/react_wrapper_view.js'></script>

View File

@ -463,7 +463,6 @@
<script type='text/javascript' src='js/registration.js'></script>
<script type='text/javascript' src='js/expire.js'></script>
<script type='text/javascript' src='js/conversation_controller.js'></script>
<script type='text/javascript' src='js/blocked_number_controller.js'></script>
<script type='text/javascript' src='js/message_controller.js'></script>
<script type='text/javascript' src='js/views/react_wrapper_view.js'></script>

View File

@ -430,8 +430,8 @@
await Promise.all([
ConversationController.load(),
textsecure.storage.protocol.hydrateCaches(),
BlockedNumberController.load(),
]);
BlockedNumberController.refresh();
} catch (error) {
window.log.error(
'background.js: ConversationController failed to load:',

View File

@ -1,68 +0,0 @@
/* 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() {
this.unblockAll();
blockedNumbers.reset([]);
},
refresh() {
window.log.info('BlockedNumberController: starting initial fetch');
if (!storage) {
throw new Error(
'BlockedNumberController: Could not load blocked numbers'
);
}
// Add the numbers to the collection
const numbers = storage.getBlockedNumbers();
blockedNumbers.reset(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;
}
storage.addBlockedNumber(number);
// Make sure we don't add duplicates
if (blockedNumbers.getModel(number)) {
return;
}
blockedNumbers.add({ number });
},
unblock(number) {
storage.removeBlockedNumber(number);
// Remove the model from our collection
const model = blockedNumbers.getModel(number);
if (model) {
blockedNumbers.remove(model);
}
},
unblockAll() {
const numbers = blockedNumbers.map(m => m.get('number'));
numbers.forEach(n => this.unblock(n));
},
isBlocked(number) {
return storage.isBlocked(number);
},
};
})();

View File

@ -195,7 +195,12 @@
},
getOrCreateAndWait(id, type) {
return this._initialPromise.then(() => {
const pubkey = id.key ? id.key : id;
if (!id) {
return Promise.reject(
new Error('getOrCreateAndWait: invalid id passed.')
);
}
const pubkey = id && id.key ? id.key : id;
const conversation = this.getOrCreate(pubkey, type);
if (conversation) {

View File

@ -224,20 +224,41 @@
return !!(this.id && this.id.match(/^rss:/));
},
isBlocked() {
return BlockedNumberController.isBlocked(this.id);
if (!this.id || this.isPublic() || this.isRss()) {
return false;
}
return this.isPrivate()
? BlockedNumberController.isBlocked(this.id)
: BlockedNumberController.isGroupBlocked(this.id);
},
isMediumGroup() {
return this.get('is_medium_group');
},
block() {
BlockedNumberController.block(this.id);
async block() {
if (!this.id || this.isPublic() || this.isRss()) {
return;
}
const promise = this.isPrivate()
? BlockedNumberController.block(this.id)
: BlockedNumberController.blockGroup(this.id);
await promise;
this.trigger('change');
this.messageCollection.forEach(m => m.trigger('change'));
this.updateTextInputState();
},
unblock() {
BlockedNumberController.unblock(this.id);
async unblock() {
if (!this.id || this.isPublic() || this.isRss()) {
return;
}
const promise = this.isPrivate()
? BlockedNumberController.unblock(this.id)
: BlockedNumberController.unblockGroup(this.id);
await promise;
this.trigger('change');
this.messageCollection.forEach(m => m.trigger('change'));
this.updateTextInputState();
},
setMessageSelectionBackdrop() {
const messageSelected = this.selectedMessages.size > 0;
@ -773,6 +794,11 @@
this.trigger('change:placeholder', 'left-group');
return;
}
if (this.isBlocked()) {
this.trigger('disable:input', true);
this.trigger('change:placeholder', 'blocked-user');
return;
}
// otherwise, enable the input and set default placeholder
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'chat');

View File

@ -898,7 +898,7 @@
ConversationController.reset();
BlockedNumberController.reset();
await ConversationController.load();
BlockedNumberController.refresh();
await BlockedNumberController.load();
},
async removeAllConfiguration() {
await window.Signal.Data.removeAllConfiguration();

View File

@ -1,102 +0,0 @@
/* global BlockedNumberController: false */
/* global getBlockedNumbers: false */
/* global Whisper: false */
/* global storage: false */
/* global i18n: 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 .unblock-button': 'onUnblock',
},
initialize() {
storage.onready(() => {
BlockedNumberController.refresh();
this.collection = getBlockedNumbers();
this.listView = new Whisper.BlockedNumberListView({
collection: this.collection,
});
this.listView.render();
this.blockedUserSettings = this.$('.blocked-user-settings');
this.blockedUserSettings.prepend(this.listView.el);
});
},
render_attributes() {
return {
blockedHeader: i18n('settingsUnblockHeader'),
unblockMessage: i18n('unblockUser'),
};
},
onUnblock() {
const number = this.$('select option:selected').val();
if (!number) {
return;
}
if (BlockedNumberController.isBlocked(number)) {
BlockedNumberController.unblock(number);
window.onUnblockNumber(number);
this.listView.collection.remove(
this.listView.collection.where({ number })
);
}
},
});
Whisper.BlockedNumberListView = Whisper.View.extend({
tagName: 'select',
initialize(options) {
this.options = options || {};
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
this.listenTo(this.collection, 'remove', this.addAll);
},
addOne(model) {
const number = model.get('number');
if (number) {
this.$el.append(
`<option value="${number}">${this.truncate(number, 25)}</option>`
);
}
},
addAll() {
this.$el.html('');
this.collection.each(this.addOne, this);
},
truncate(string, limit) {
// Make sure an element and number of items to truncate is provided
if (!string || !limit) {
return string;
}
// Get the inner content of the element
let content = string.trim();
// Convert the content into an array of words
// Remove any words above the limit
content = content.slice(0, limit);
// Convert the array of words back into a string
// If there's content to add after it, add it
if (string.length > limit) {
content = `${content}...`;
}
return content;
},
render() {
this.addAll();
return this;
},
});
})();

View File

@ -535,6 +535,9 @@
case 'left-group':
placeholder = i18n('sendMessageLeftGroup');
break;
case 'blocked-user':
placeholder = i18n('sendMessageBlockedUser');
break;
default:
placeholder = i18n('sendMessage');
break;

View File

@ -194,7 +194,7 @@ window.resetDatabase = () => {
ipc.send('resetDatabase');
};
window.onUnblockNumber = number => {
window.onUnblockNumber = async number => {
// Unblock the number
if (window.BlockedNumberController) {
window.BlockedNumberController.unblock(number);
@ -204,7 +204,7 @@ window.onUnblockNumber = number => {
if (window.ConversationController) {
try {
const conversation = window.ConversationController.get(number);
conversation.unblock();
await conversation.unblock();
} catch (e) {
window.log.info(
'IPC on unblock: failed to fetch conversation for number: ',
@ -509,3 +509,11 @@ if (config.environment.includes('test-integration')) {
enableSenderKeys: true,
};
}
// Blocking
const {
BlockedNumberController,
} = require('./ts/util/blockedNumberController');
window.BlockedNumberController = BlockedNumberController;

View File

@ -1,163 +0,0 @@
/* global textsecure, BlockedNumberController, storage */
'use strict';
describe('Blocked Number Controller', () => {
beforeEach(async () => {
// Purge everything manually
const numbers = storage.getBlockedNumbers();
numbers.forEach(storage.removeBlockedNumber);
window.getBlockedNumbers().reset([]);
});
describe('reset', () => {
it('clears blocked numbers', () => {
BlockedNumberController.block('1');
assert.isNotEmpty(storage.getBlockedNumbers());
assert.isNotEmpty(window.getBlockedNumbers().models);
BlockedNumberController.reset();
assert.isEmpty(storage.getBlockedNumbers());
assert.isEmpty(window.getBlockedNumbers().models);
});
});
describe('refresh', () => {
it('loads blocked numbers from storage', () => {
BlockedNumberController.refresh();
assert.isEmpty(window.getBlockedNumbers().models);
storage.addBlockedNumber('1');
storage.addBlockedNumber('2');
BlockedNumberController.refresh();
const blocked = window.getBlockedNumbers().map(m => m.get('number'));
assert.lengthOf(blocked, 2);
assert.deepEqual(['1', '2'], blocked.sort());
});
it('overrides old numbers if we refresh again', () => {
BlockedNumberController.refresh();
assert.isEmpty(window.getBlockedNumbers().models);
storage.addBlockedNumber('1');
BlockedNumberController.refresh();
assert.isNotEmpty(
window.getBlockedNumbers().find(m => m.get('number') === '1')
);
storage.removeBlockedNumber('1');
storage.addBlockedNumber('2');
BlockedNumberController.refresh();
assert.isNotEmpty(
window.getBlockedNumbers().find(m => m.get('number') === '2')
);
});
it('throws if storage is invalid', () => {
const _storage = window.storage;
window.storage = null;
assert.throws(
() => BlockedNumberController.refresh(),
'BlockedNumberController: Could not load blocked numbers'
);
window.storage = _storage;
});
});
describe('block', () => {
beforeEach(() => {
BlockedNumberController.refresh();
assert.isEmpty(storage.getBlockedNumbers());
assert.isEmpty(window.getBlockedNumbers().models);
});
it('adds number to the blocked list', () => {
BlockedNumberController.block('1');
const numbers = window.getBlockedNumbers().models;
assert.lengthOf(numbers, 1);
assert.strictEqual('1', numbers[0].get('number'));
assert.deepEqual(['1'], storage.getBlockedNumbers());
});
it('only blocks the same number once', () => {
BlockedNumberController.block('2');
BlockedNumberController.block('2');
assert.lengthOf(window.getBlockedNumbers().models, 1);
assert.deepEqual(['2'], storage.getBlockedNumbers());
});
it('does not block our own number', () => {
BlockedNumberController.block(textsecure.storage.user.getNumber());
assert.isEmpty(window.getBlockedNumbers().models);
assert.isEmpty(storage.getBlockedNumbers());
});
});
describe('unblock', () => {
beforeEach(() => {
BlockedNumberController.refresh();
assert.isEmpty(storage.getBlockedNumbers());
assert.isEmpty(window.getBlockedNumbers().models);
});
it('removes number from the blocked list', () => {
BlockedNumberController.block('1');
BlockedNumberController.block('2');
assert.lengthOf(window.getBlockedNumbers().models, 2);
assert.lengthOf(storage.getBlockedNumbers(), 2);
BlockedNumberController.unblock('1');
const numbers = window.getBlockedNumbers().models;
assert.lengthOf(numbers, 1);
assert.isEmpty(numbers.filter(n => n.get('number') === '1'));
assert.deepEqual(['2'], storage.getBlockedNumbers());
});
it('removes number from the blocked list even if it is not present in the collection', () => {
BlockedNumberController.block('1');
BlockedNumberController.block('2');
window.getBlockedNumbers().reset([]);
assert.isEmpty(window.getBlockedNumbers().models);
assert.lengthOf(storage.getBlockedNumbers(), 2);
BlockedNumberController.unblock('1');
assert.deepEqual(['2'], storage.getBlockedNumbers());
});
});
describe('unblockAll', () => {
it('removes all our blocked numbers', () => {
BlockedNumberController.refresh();
BlockedNumberController.block('1');
BlockedNumberController.block('2');
BlockedNumberController.block('3');
assert.lengthOf(window.getBlockedNumbers().models, 3);
assert.lengthOf(storage.getBlockedNumbers(), 3);
BlockedNumberController.unblockAll();
assert.lengthOf(window.getBlockedNumbers().models, 0);
assert.lengthOf(storage.getBlockedNumbers(), 0);
});
});
describe('isBlocked', () => {
it('returns whether a number is blocked', () => {
BlockedNumberController.refresh();
BlockedNumberController.block('1');
assert.isOk(BlockedNumberController.isBlocked('1'));
assert.isNotOk(BlockedNumberController.isBlocked('2'));
BlockedNumberController.unblock('1');
assert.isNotOk(BlockedNumberController.isBlocked('1'));
});
});
});

View File

@ -505,7 +505,6 @@
<script type="text/javascript" src="../js/models/conversations.js" data-cover></script>
<script type="text/javascript" src="../js/models/blockedNumbers.js" data-cover></script>
<script type="text/javascript" src="../js/conversation_controller.js" data-cover></script>
<script type="text/javascript" src="../js/blocked_number_controller.js"></script>
<script type="text/javascript" src="../js/message_controller.js" data-cover></script>
<script type="text/javascript" src="../js/keychange_listener.js" data-cover></script>
<script type="text/javascript" src="../js/expiring_messages.js" data-cover></script>
@ -574,7 +573,6 @@
<script type="text/javascript" src="models/conversations_test.js"></script>
<script type="text/javascript" src="models/messages_test.js"></script>
<script type="text/javascript" src="blocked_number_controller_test.js"></script>
<script type="text/javascript" src="conversation_controller_test.js"></script>
<script type="text/javascript" src="storage_test.js"></script>
<script type="text/javascript" src="keychange_listener_test.js"></script>

View File

@ -18,6 +18,7 @@ import { PubKey } from '../session/types';
import { handleSyncMessage } from './syncMessages';
import { onError } from './errors';
import ByteBuffer from 'bytebuffer';
import { BlockedNumberController } from '../util/blockedNumberController';
export async function handleContentMessage(envelope: EnvelopePlus) {
const plaintext = await decrypt(envelope, envelope.content);
@ -96,9 +97,7 @@ function unpad(paddedData: ArrayBuffer): ArrayBuffer {
}
export function isBlocked(number: string) {
// TODO: should probably use primary pubkeys here!
const blockedNumbers = window.textsecure.storage.get('blocked', []);
return blockedNumbers.indexOf(number) >= 0;
return BlockedNumberController.isBlocked(number);
}
async function decryptPreKeyWhisperMessage(

View File

@ -3,11 +3,10 @@ import { ClosedGroupRequestInfoMessage } from '../session/messages/outgoing/cont
import { getMessageQueue } from '../session';
import { PubKey } from '../session/types';
import _ from 'lodash';
import { BlockedNumberController } from '../util/blockedNumberController';
function isGroupBlocked(groupId: string) {
return (
window.textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0
);
return BlockedNumberController.isGroupBlocked(groupId);
}
function shouldIgnoreBlockedGroup(group: any, senderPubKey: string) {

View File

@ -0,0 +1,119 @@
import { createOrUpdateItem, getItemById } from '../../js/modules/data';
import { PubKey } from '../session/types';
import { MultiDeviceProtocol } from '../session/protocols';
const BLOCKED_NUMBERS_ID = 'blocked';
const BLOCKED_GROUPS_ID = 'blocked-groups';
// tslint:disable-next-line: no-unnecessary-class
export class BlockedNumberController {
private static loaded: boolean = false;
private static blockedNumbers: Set<string> = new Set();
private static blockedGroups: Set<string> = new Set();
/**
* Check if a device is blocked.
*
* @param number The device.
*/
public static isBlocked(device: string | PubKey): boolean {
void this.load();
const stringValue =
device instanceof PubKey ? device.key : device.toLowerCase();
return this.blockedNumbers.has(stringValue);
}
/**
* Check if a group id is blocked.
* @param groupId The group id.
*/
public static isGroupBlocked(groupId: string | PubKey): boolean {
void this.load();
const stringValue =
groupId instanceof PubKey ? groupId.key : groupId.toLowerCase();
return this.blockedGroups.has(stringValue);
}
/**
* Block a user (including their linked devices).
*
* @param user The user to block.
*/
public static async block(user: string | PubKey): Promise<void> {
// The reason we add all linked device to block number set instead of checking if any device of a user is in the `isBlocked` function because
// `isBlocked` is used synchronously in the code. To check if any device is blocked needs it to be async, which would mean all calls to `isBlocked` will also need to be async and so on
// This is too much of a hassle at the moment as some UI code will have to be migrated to work with this async call.
await this.load();
const devices = await MultiDeviceProtocol.getAllDevices(user);
devices.forEach(pubKey => this.blockedNumbers.add(pubKey.key));
await this.saveToDB(BLOCKED_NUMBERS_ID, this.blockedNumbers);
}
/**
* Unblock a user (including their linked devices).
* @param user The user to unblock.
*/
public static async unblock(user: string | PubKey): Promise<void> {
await this.load();
const devices = await MultiDeviceProtocol.getAllDevices(user);
devices.forEach(pubKey => this.blockedNumbers.delete(pubKey.key));
await this.saveToDB(BLOCKED_NUMBERS_ID, this.blockedNumbers);
}
public static async blockGroup(groupId: string | PubKey): Promise<void> {
await this.load();
const id = PubKey.cast(groupId);
this.blockedGroups.add(id.key);
await this.saveToDB(BLOCKED_GROUPS_ID, this.blockedGroups);
}
public static async unblockGroup(groupId: string | PubKey): Promise<void> {
await this.load();
const id = PubKey.cast(groupId);
this.blockedGroups.delete(id.key);
await this.saveToDB(BLOCKED_GROUPS_ID, this.blockedGroups);
}
public static getBlockedNumbers(): Set<string> {
return new Set(this.blockedNumbers);
}
public static getBlockedGroups(): Set<string> {
return new Set(this.blockedGroups);
}
// ---- DB
public static async load() {
if (!this.loaded) {
this.blockedNumbers = await this.getNumbersFromDB(BLOCKED_NUMBERS_ID);
this.blockedGroups = await this.getNumbersFromDB(BLOCKED_GROUPS_ID);
this.loaded = true;
}
}
public static async reset() {
this.loaded = false;
this.blockedNumbers = new Set();
this.blockedGroups = new Set();
}
private static async getNumbersFromDB(id: string): Promise<Set<string>> {
const data = await getItemById(id);
if (!data || !data.value) {
return new Set();
}
return new Set(data.value);
}
private static async saveToDB(
id: string,
numbers: Set<string>
): Promise<void> {
await createOrUpdateItem({
id,
value: [...numbers],
});
}
}

View File

@ -6,6 +6,8 @@ import { migrateColor } from './migrateColor';
import { makeLookup } from './makeLookup';
import * as UserUtil from './user';
export * from './blockedNumberController';
export {
arrayBufferToObjectURL,
GoogleChrome,