Heap of linting, eslint warning/error removal, and fixed a couple small bugs found along the way

Created new table to store the received message hashes. Checking this table when receiving messages to look for duplicates. Should be cleared of expired messages on app start and every hour after

Removed id which was not needed for seen messages. Refactored filter logic into function and found function name error

create unique index for contact prekeys (to allow using REPLACE)

Fixed lint stuff that merge brought back
pull/48/head
Beaudan 7 years ago
parent 2188617d2f
commit 489ec8fc65

@ -89,6 +89,9 @@ module.exports = {
getMessageCount, getMessageCount,
saveMessage, saveMessage,
cleanSeenMessages,
saveSeenMessageHashes,
saveSeenMessageHash,
saveMessages, saveMessages,
removeMessage, removeMessage,
getUnreadByConversation, getUnreadByConversation,
@ -98,6 +101,7 @@ module.exports = {
getAllMessageIds, getAllMessageIds,
getAllUnsentMessages, getAllUnsentMessages,
getMessagesBySentAt, getMessagesBySentAt,
getSeenMessagesByHashList,
getExpiredMessages, getExpiredMessages,
getOutgoingWithoutExpiresAt, getOutgoingWithoutExpiresAt,
getNextExpiringMessage, getNextExpiringMessage,
@ -390,6 +394,13 @@ async function updateToSchemaVersion6(currentVersion, instance) {
console.log('updateToSchemaVersion6: starting...'); console.log('updateToSchemaVersion6: starting...');
await instance.run('BEGIN TRANSACTION;'); await instance.run('BEGIN TRANSACTION;');
await instance.run(
`CREATE TABLE seenMessages(
hash STRING PRIMARY KEY,
expiresAt INTEGER
);`
);
// key-value, ids are strings, one extra column // key-value, ids are strings, one extra column
await instance.run( await instance.run(
`CREATE TABLE sessions( `CREATE TABLE sessions(
@ -447,6 +458,11 @@ async function updateToSchemaVersion6(currentVersion, instance) {
);` );`
); );
await instance.run(`CREATE UNIQUE INDEX contact_prekey_identity_key_string_keyid ON contactPreKeys (
identityKeyString,
keyId
);`);
await instance.run( await instance.run(
`CREATE TABLE contactSignedPreKeys( `CREATE TABLE contactSignedPreKeys(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
@ -456,6 +472,11 @@ async function updateToSchemaVersion6(currentVersion, instance) {
);` );`
); );
await instance.run(`CREATE UNIQUE INDEX contact_signed_prekey_identity_key_string_keyid ON contactSignedPreKeys (
identityKeyString,
keyId
);`);
await instance.run('PRAGMA schema_version = 6;'); await instance.run('PRAGMA schema_version = 6;');
await instance.run('COMMIT TRANSACTION;'); await instance.run('COMMIT TRANSACTION;');
console.log('updateToSchemaVersion6: success!'); console.log('updateToSchemaVersion6: success!');
@ -1118,6 +1139,7 @@ async function saveMessage(data, { forceSave } = {}) {
schemaVersion, schemaVersion,
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
sent, sent,
// eslint-disable-next-line camelcase
sent_at, sent_at,
source, source,
sourceDevice, sourceDevice,
@ -1230,6 +1252,45 @@ async function saveMessage(data, { forceSave } = {}) {
return toCreate.id; return toCreate.id;
} }
async function saveSeenMessageHashes(arrayOfHashes) {
let promise;
db.serialize(() => {
promise = Promise.all([
db.run('BEGIN TRANSACTION;'),
...map(arrayOfHashes, hashData => saveSeenMessageHash(hashData)),
db.run('COMMIT TRANSACTION;'),
]);
});
await promise;
}
async function saveSeenMessageHash(data) {
const {
expiresAt,
hash,
} = data;
await db.run(
`INSERT INTO seenMessages (
expiresAt,
hash
) values (
$expiresAt,
$hash
);`, {
$expiresAt: expiresAt,
$hash: hash,
}
);
}
async function cleanSeenMessages() {
await db.run('DELETE FROM seenMessages WHERE expiresAt <= $now;', {
$now: Date.now(),
});
}
async function saveMessages(arrayOfMessages, { forceSave } = {}) { async function saveMessages(arrayOfMessages, { forceSave } = {}) {
let promise; let promise;
@ -1360,6 +1421,15 @@ async function getMessagesBySentAt(sentAt) {
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }
async function getSeenMessagesByHashList(hashes) {
const rows = await db.all(
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes.map(() => '?').join(', ')} );`,
hashes
);
return map(rows, row => row.hash);
}
async function getExpiredMessages() { async function getExpiredMessages() {
const now = Date.now(); const now = Date.now();

@ -7,12 +7,11 @@
Signal, Signal,
storage, storage,
textsecure, textsecure,
WebAPI
Whisper, Whisper,
*/ */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(async function() { (async function () {
'use strict'; 'use strict';
// Globally disable drag and drop // Globally disable drag and drop
@ -325,7 +324,7 @@
// Combine the models // Combine the models
const messagesForCleanup = results.reduce((array, current) => array.concat(current.toArray()), []); const messagesForCleanup = results.reduce((array, current) => array.concat(current.toArray()), []);
window.log.info( window.log.info(
`Cleanup: Found ${messagesForCleanup.length} messages for cleanup` `Cleanup: Found ${messagesForCleanup.length} messages for cleanup`
); );
@ -376,7 +375,7 @@
let isMigrationWithIndexComplete = false; let isMigrationWithIndexComplete = false;
window.log.info( window.log.info(
`Starting background data migration. Target version: ${ `Starting background data migration. Target version: ${
Message.CURRENT_SCHEMA_VERSION Message.CURRENT_SCHEMA_VERSION
}` }`
); );
idleDetector.on('idle', async () => { idleDetector.on('idle', async () => {
@ -464,7 +463,13 @@
} }
}); });
function manageSeenMessages() {
window.Signal.Data.cleanSeenMessages();
setTimeout(manageSeenMessages, 1000 * 60 * 60);
}
async function start() { async function start() {
manageSeenMessages();
window.dispatchEvent(new Event('storage_ready')); window.dispatchEvent(new Event('storage_ready'));
window.log.info('listening for registration events'); window.log.info('listening for registration events');
@ -559,7 +564,7 @@
// Gets called when a user accepts or declines a friend request // Gets called when a user accepts or declines a friend request
Whisper.events.on('friendRequestUpdated', friendRequest => { Whisper.events.on('friendRequestUpdated', friendRequest => {
const { pubKey, ...message } = friendRequest; const { pubKey, ...message } = friendRequest;
if (messageReceiver) { if (messageReceiver) {
messageReceiver.onFriendRequestUpdate(pubKey, message); messageReceiver.onFriendRequestUpdate(pubKey, message);
} }
@ -571,11 +576,13 @@
} }
}); });
Whisper.events.on('calculatingPoW', ({ pubKey, timestamp}) => { Whisper.events.on('calculatingPoW', ({ pubKey, timestamp }) => {
try { try {
const conversation = ConversationController.get(pubKey); const conversation = ConversationController.get(pubKey);
conversation.onCalculatingPoW(pubKey, timestamp); conversation.onCalculatingPoW(pubKey, timestamp);
} catch (e) {} } catch (e) {
window.log.error('Error showing PoW cog');
}
}); });
} }
@ -1283,7 +1290,7 @@
} catch (error) { } catch (error) {
window.log.error( window.log.error(
`Failed to send delivery receipt to ${data.source} for message ${ `Failed to send delivery receipt to ${data.source} for message ${
data.timestamp data.timestamp
}:`, }:`,
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );

@ -1,8 +1,7 @@
/* global _: false */ /* global _: false */
/* global Backbone: false */ /* global Backbone: false */
/* global libphonenumber: false */
/* global ConversationController: false */ /* global ConversationController: false */
/* global i18n: false */
/* global libsignal: false */ /* global libsignal: false */
/* global storage: false */ /* global storage: false */
/* global textsecure: false */ /* global textsecure: false */
@ -11,7 +10,7 @@
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function () {
'use strict'; 'use strict';
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
@ -129,7 +128,7 @@
setTimeout(() => { setTimeout(() => {
this.setFriendRequestTimer(); this.setFriendRequestTimer();
}, 0); }, 0);
const sealedSender = this.get('sealedSender'); const sealedSender = this.get('sealedSender');
if (sealedSender === undefined) { if (sealedSender === undefined) {
this.set({ sealedSender: SEALED_SENDER.UNKNOWN }); this.set({ sealedSender: SEALED_SENDER.UNKNOWN });
@ -239,37 +238,39 @@
} }
}, },
// This goes through all our message history and finds a friend request // This goes through all our message history and finds a friend request
// But this is not a concurrent operation and thus `updatePendingFriendRequests` is used // But this is not a concurrent operation and thus updatePendingFriendRequests is used
async hasPendingFriendRequests() { async hasPendingFriendRequests() {
// Go through the messages and check for any pending friend requests // Go through the messages and check for any pending friend requests
const messages = await window.Signal.Data.getMessagesByConversation( const messages = await window.Signal.Data.getMessagesByConversation(
this.id, this.id,
{ {
type: 'friend-request', type: 'friend-request',
MessageCollection: Whisper.MessageCollection, MessageCollection: Whisper.MessageCollection,
} }
); );
const pendingFriendRequest =
for (const message of messages.models) { messages.models.find(message =>
if (message.isFriendRequest() && message.attributes.friendStatus === 'pending') return true; message.isFriendRequest() &&
} message.attributes.friendStatus === 'pending'
);
return false; return pendingFriendRequest !== undefined;
}, },
async getPendingFriendRequests(direction) { async getPendingFriendRequests(direction) {
// Theoretically all ouur messages could be friend requests, thus we have to unfortunately go through each one :( // Theoretically all our messages could be friend requests,
// thus we have to unfortunately go through each one :(
const messages = await window.Signal.Data.getMessagesByConversation( const messages = await window.Signal.Data.getMessagesByConversation(
this.id, this.id,
{ {
type: 'friend-request', type: 'friend-request',
MessageCollection: Whisper.MessageCollection, MessageCollection: Whisper.MessageCollection,
} }
); );
// Get the messages that are matching the direction and the friendStatus // Get the messages that are matching the direction and the friendStatus
return messages.models.filter(m => { return messages.models.filter(m =>
return (m.attributes.direction === direction && m.attributes.friendStatus === 'pending') m.attributes.direction === direction &&
}); m.attributes.friendStatus === 'pending'
);
}, },
getPropsForListItem() { getPropsForListItem() {
const result = { const result = {
@ -351,7 +352,7 @@
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error( throw new Error(
'You cannot verify a group conversation. ' + 'You cannot verify a group conversation. ' +
'You must verify individual contacts.' 'You must verify individual contacts.'
); );
} }
@ -469,7 +470,9 @@
}, },
async onFriendRequestAccepted({ updateUnread }) { async onFriendRequestAccepted({ updateUnread }) {
// Make sure we don't keep incrementing the unread count // Make sure we don't keep incrementing the unread count
const unreadCount = this.isKeyExchangeCompleted() || !updateUnread ? {} : { unreadCount: this.get('unreadCount') + 1 }; const unreadCount = !updateUnread || this.isKeyExchangeCompleted()
? {}
: { unreadCount: this.get('unreadCount') + 1 };
this.set({ this.set({
friendRequestStatus: null, friendRequestStatus: null,
keyExchangeCompleted: true, keyExchangeCompleted: true,
@ -528,7 +531,7 @@
friendRequestStatus.allowSending = false; friendRequestStatus.allowSending = false;
const delayMs = 60 * 60 * 1000 * friendRequestLockDuration; const delayMs = 60 * 60 * 1000 * friendRequestLockDuration;
friendRequestStatus.unlockTimestamp = Date.now() + delayMs; friendRequestStatus.unlockTimestamp = Date.now() + delayMs;
// Update the text input state // Update the text input state
this.updateTextInputState(); this.updateTextInputState();
@ -580,7 +583,7 @@
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error( throw new Error(
'You cannot set a group conversation as trusted. ' + 'You cannot set a group conversation as trusted. ' +
'You must set individual contacts as trusted.' 'You must set individual contacts as trusted.'
); );
} }
@ -738,17 +741,16 @@
// This is to ensure that one user cannot spam us with multiple friend requests // This is to ensure that one user cannot spam us with multiple friend requests
if (_options.direction === 'incoming') { if (_options.direction === 'incoming') {
const requests = await this.getPendingFriendRequests('incoming'); const requests = await this.getPendingFriendRequests('incoming');
for (const request of requests) { // Delete the old message if it's pending
// Delete the old message if it's pending await Promise.all(requests.map(async request => this._removeMessage(request.id)));
await this._removeMessage(request.id);
}
// Trigger an update if we removed messages // Trigger an update if we removed messages
if (requests.length > 0) if (requests.length > 0)
this.trigger('change'); this.trigger('change');
} }
// Add the new message // Add the new message
// eslint-disable-next-line camelcase
const received_at = _options.received_at || Date.now(); const received_at = _options.received_at || Date.now();
const message = { const message = {
conversationId: this.id, conversationId: this.id,
@ -770,11 +772,11 @@
Message: Whisper.Message, Message: Whisper.Message,
}); });
const whisperMessage = new Whisper.Message({ const whisperMessage = new Whisper.Message({
...message, ...message,
id, id,
}); });
this.trigger('newmessage', whisperMessage); this.trigger('newmessage', whisperMessage);
this.notify(whisperMessage); this.notify(whisperMessage);
}, },
@ -978,9 +980,9 @@
fileName: fileName || null, fileName: fileName || null,
thumbnail: thumbnail thumbnail: thumbnail
? { ? {
...(await loadAttachmentData(thumbnail)), ...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path), objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
} }
: null, : null,
}; };
}) })
@ -1007,7 +1009,7 @@
'with timestamp', 'with timestamp',
now now
); );
let messageWithSchema = null; let messageWithSchema = null;
// If we have exchanged keys then let the user send the message normally // If we have exchanged keys then let the user send the message normally
@ -1024,26 +1026,31 @@
recipients, recipients,
}); });
} else { } else {
// We also need to make sure we don't send a new friend request if we already have an existing one // We also need to make sure we don't send a new friend request
const incomingRequests = await this.getPendingFriendRequests('incoming'); // if we already have an existing one
if (incomingRequests.length > 0) return; const incomingRequests = await this.getPendingFriendRequests('incoming');
if (incomingRequests.length > 0) return null;
// Otherwise check if we have sent a friend request // Otherwise check if we have sent a friend request
const outgoingRequests = await this.getPendingFriendRequests('outgoing'); const outgoingRequests = await this.getPendingFriendRequests('outgoing');
if (outgoingRequests.length > 0) { if (outgoingRequests.length > 0) {
// Check if the requests have errored, if so then remove them and send the new request if possible // Check if the requests have errored, if so then remove them
const friendRequestSent = false; // and send the new request if possible
for (const outgoing of outgoingRequests) { let friendRequestSent = false;
const promises = [];
outgoingRequests.forEach(async outgoing => {
if (outgoing.hasErrors()) { if (outgoing.hasErrors()) {
await this._removeMessage(outgoing.id); promises.push(this._removeMessage(outgoing.id));
} else { } else {
// No errors = we have sent over the friend request // No errors = we have sent over the friend request
friendRequestSent = true; friendRequestSent = true;
} }
} });
await Promise.all(promises);
// If the requests didn't error then don't add a new friend request because one of them was sent successfully // If the requests didn't error then don't add a new friend request
if (friendRequestSent) return; // because one of them was sent successfully
if (friendRequestSent) return null;
} }
// Send the friend request! // Send the friend request!
@ -1114,8 +1121,8 @@
const options = this.getSendOptions(); const options = this.getSendOptions();
// Add the message sending on another queue so that our UI doesn't get blocked // Add the message sending on another queue so that our UI doesn't get blocked
this.queueMessageSend(async () => { this.queueMessageSend(async () =>
return message.send( message.send(
this.wrapSend( this.wrapSend(
sendFunction( sendFunction(
destination, destination,
@ -1128,8 +1135,8 @@
options options
) )
) )
); )
}); );
return true; return true;
}); });
@ -1148,12 +1155,11 @@
this.trigger('disable:input', true); this.trigger('disable:input', true);
this.trigger('change:placeholder', 'disabled'); this.trigger('change:placeholder', 'disabled');
return; return;
} else {
// Tell the user to introduce themselves
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'friend-request');
return;
} }
// Tell the user to introduce themselves
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'friend-request');
return;
} }
this.trigger('disable:input', false); this.trigger('disable:input', false);
this.trigger('change:placeholder', 'chat'); this.trigger('change:placeholder', 'chat');
@ -1303,8 +1309,8 @@
accessKey && sealedSender === SEALED_SENDER.ENABLED accessKey && sealedSender === SEALED_SENDER.ENABLED
? accessKey ? accessKey
: window.Signal.Crypto.arrayBufferToBase64( : window.Signal.Crypto.arrayBufferToBase64(
window.Signal.Crypto.getRandomBytes(16) window.Signal.Crypto.getRandomBytes(16)
), ),
}, },
}; };
}, },
@ -1583,7 +1589,7 @@
} else { } else {
window.log.warn( window.log.warn(
'Marked a message as read in the database, but ' + 'Marked a message as read in the database, but ' +
'it was not in messageCollection.' 'it was not in messageCollection.'
); );
} }
@ -2104,8 +2110,9 @@
// Notification for friend request received // Notification for friend request received
async notifyFriendRequest(source, type) { async notifyFriendRequest(source, type) {
// Data validation // Data validation
if (!source) return Promise.reject('Invalid source'); if (!source) return Promise.reject(new Error('Invalid source'));
if (!['accepted', 'requested'].includes(type)) return Promise.reject('Type must be accepted or requested.'); if (!['accepted', 'requested'].includes(type))
return Promise.reject(new Error('Type must be accepted or requested.'));
// Call the notification on the right conversation // Call the notification on the right conversation
let conversation = this; let conversation = this;
@ -2115,29 +2122,34 @@
source, source,
'private' 'private'
); );
window.log.info(`Notify called on a different conversation. expected: ${this.id}. actual: ${conversation.id}`); window.log.info(`Notify called on a different conversation.
Expected: ${this.id}. Actual: ${conversation.id}`);
} catch (e) { } catch (e) {
return Promise.reject('Failed to fetch conversation'); return Promise.reject(new Error('Failed to fetch conversation'));
} }
} }
const isTypeAccepted = type === 'accepted'; const isTypeAccepted = type === 'accepted';
const title = isTypeAccepted ? 'friendRequestAcceptedNotificationTitle' : 'friendRequestNotificationTitle'; const title = isTypeAccepted
const message = isTypeAccepted ? 'friendRequestAcceptedNotificationMessage' : 'friendRequestNotificationMessage'; ? 'friendRequestAcceptedNotificationTitle'
: 'friendRequestNotificationTitle';
conversation.getNotificationIcon().then(iconUrl => { const message = isTypeAccepted
window.log.info('Add notification for friend request updated', { ? 'friendRequestAcceptedNotificationMessage'
conversationId: conversation.idForLogging(), : 'friendRequestNotificationMessage';
});
Whisper.Notifications.add({ const iconUrl = await conversation.getNotificationIcon();
conversationId: conversation.id, window.log.info('Add notification for friend request updated', {
iconUrl, conversationId: conversation.idForLogging(),
isExpiringMessage: false, });
message: i18n(message, conversation.getTitle()), Whisper.Notifications.add({
messageSentAt: Date.now(), conversationId: conversation.id,
title: i18n(title), iconUrl,
}); isExpiringMessage: false,
}); message: i18n(message, conversation.getTitle()),
messageSentAt: Date.now(),
title: i18n(title),
});
return Promise.resolve();
}, },
}); });

@ -12,7 +12,7 @@
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function () {
'use strict'; 'use strict';
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
@ -536,8 +536,8 @@
contact.number && contact.number[0] && contact.number[0].value; contact.number && contact.number[0] && contact.number[0].value;
const onSendMessage = firstNumber const onSendMessage = firstNumber
? () => { ? () => {
this.trigger('open-conversation', firstNumber); this.trigger('open-conversation', firstNumber);
} }
: null; : null;
const onClick = async () => { const onClick = async () => {
// First let's be sure that the signal account check is complete. // First let's be sure that the signal account check is complete.
@ -576,8 +576,8 @@
!path && !objectUrl !path && !objectUrl
? null ? null
: Object.assign({}, attachment.thumbnail || {}, { : Object.assign({}, attachment.thumbnail || {}, {
objectUrl: path || objectUrl, objectUrl: path || objectUrl,
}); });
return Object.assign({}, attachment, { return Object.assign({}, attachment, {
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment), isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
@ -644,15 +644,15 @@
url: getAbsoluteAttachmentPath(path), url: getAbsoluteAttachmentPath(path),
screenshot: screenshot screenshot: screenshot
? { ? {
...screenshot, ...screenshot,
url: getAbsoluteAttachmentPath(screenshot.path), url: getAbsoluteAttachmentPath(screenshot.path),
} }
: null, : null,
thumbnail: thumbnail thumbnail: thumbnail
? { ? {
...thumbnail, ...thumbnail,
url: getAbsoluteAttachmentPath(thumbnail.path), url: getAbsoluteAttachmentPath(thumbnail.path),
} }
: null, : null,
}; };
}, },
@ -1393,7 +1393,7 @@
if (previousUnread !== message.get('unread')) { if (previousUnread !== message.get('unread')) {
window.log.warn( window.log.warn(
'Caught race condition on new message read state! ' + 'Caught race condition on new message read state! ' +
'Manually starting timers.' 'Manually starting timers.'
); );
// We call markRead() even though the message is already // We call markRead() even though the message is already
// marked read because we need to start expiration // marked read because we need to start expiration

@ -122,6 +122,9 @@ module.exports = {
getMessageCount, getMessageCount,
saveMessage, saveMessage,
cleanSeenMessages,
saveSeenMessageHash,
saveSeenMessageHashes,
saveLegacyMessage, saveLegacyMessage,
saveMessages, saveMessages,
removeMessage, removeMessage,
@ -140,6 +143,7 @@ module.exports = {
getOutgoingWithoutExpiresAt, getOutgoingWithoutExpiresAt,
getNextExpiringMessage, getNextExpiringMessage,
getMessagesByConversation, getMessagesByConversation,
getSeenMessagesByHashList,
getUnprocessedCount, getUnprocessedCount,
getAllUnprocessed, getAllUnprocessed,
@ -728,6 +732,18 @@ async function getMessageCount() {
return channels.getMessageCount(); return channels.getMessageCount();
} }
async function cleanSeenMessages() {
await channels.cleanSeenMessages();
}
async function saveSeenMessageHashes(data) {
await channels.saveSeenMessageHashes(_cleanData(data));
}
async function saveSeenMessageHash(data) {
await channels.saveSeenMessageHash(_cleanData(data));
}
async function saveMessage(data, { forceSave, Message } = {}) { async function saveMessage(data, { forceSave, Message } = {}) {
const updated = keysFromArrayBuffer(MESSAGE_PRE_KEYS, data); const updated = keysFromArrayBuffer(MESSAGE_PRE_KEYS, data);
const id = await channels.saveMessage(_cleanData(updated), { forceSave }); const id = await channels.saveMessage(_cleanData(updated), { forceSave });
@ -861,6 +877,13 @@ async function getMessagesByConversation(
return new MessageCollection(encoded); return new MessageCollection(encoded);
} }
async function getSeenMessagesByHashList(
hashes
) {
const seenMessages = await channels.getSeenMessagesByHashList(hashes);
return seenMessages;
}
async function removeAllMessagesInConversation( async function removeAllMessagesInConversation(
conversationId, conversationId,
{ MessageCollection } { MessageCollection }

@ -1,4 +1,4 @@
/* global log, dcodeIO */ /* global log, dcodeIO, window */
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const is = require('@sindresorhus/is'); const is = require('@sindresorhus/is');

@ -1,7 +1,7 @@
/* global window, dcodeIO, textsecure, StringView */ /* global window, dcodeIO, textsecure, StringView */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function () {
let server; let server;
function stringToArrayBufferBase64(string) { function stringToArrayBufferBase64(string) {
@ -40,6 +40,17 @@
}; };
}; };
const filterIncomingMessages = async function filterIncomingMessages(messages) {
const incomingHashes = messages.map(m => m.hash);
const dupHashes = await window.Signal.Data.getSeenMessagesByHashList(incomingHashes);
const newMessages = messages.filter(m => !dupHashes.includes(m.hash));
const newHashes = newMessages.map(m => ({
expiresAt: m.expiration,
hash: m.hash,
}));
await window.Signal.Data.saveSeenMessageHashes(newHashes);
return newMessages;
};
window.HttpResource = function HttpResource(_server, opts = {}) { window.HttpResource = function HttpResource(_server, opts = {}) {
server = _server; server = _server;
@ -56,7 +67,7 @@
try { try {
result = await server.retrieveMessages(pubKey); result = await server.retrieveMessages(pubKey);
connected = true; connected = true;
} catch(err) { } catch (err) {
connected = false; connected = false;
setTimeout(() => { pollServer(callBack); }, 5000); setTimeout(() => { pollServer(callBack); }, 5000);
return; return;
@ -68,7 +79,8 @@
setTimeout(() => { pollServer(callBack); }, 5000); setTimeout(() => { pollServer(callBack); }, 5000);
return; return;
} }
result.messages.forEach(async message => { const newMessages = await filterIncomingMessages(result.messages);
newMessages.forEach(async message => {
const { data } = message; const { data } = message;
const dataPlaintext = stringToArrayBufferBase64(data); const dataPlaintext = stringToArrayBufferBase64(data);
const messageBuf = textsecure.protobuf.WebSocketMessage.decode(dataPlaintext); const messageBuf = textsecure.protobuf.WebSocketMessage.decode(dataPlaintext);

@ -1,6 +1,7 @@
/* global window: false */ /* global window: false */
/* global textsecure: false */ /* global textsecure: false */
/* global StringView: false */ /* global StringView: false */
/* global libloki: false */
/* global libsignal: false */ /* global libsignal: false */
/* global WebSocket: false */ /* global WebSocket: false */
/* global Event: false */ /* global Event: false */
@ -10,8 +11,10 @@
/* global ContactBuffer: false */ /* global ContactBuffer: false */
/* global GroupBuffer: false */ /* global GroupBuffer: false */
/* global Worker: false */ /* global Worker: false */
/* global WebSocketResource: false */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
/* eslint-disable no-unreachable */
const WORKER_TIMEOUT = 60 * 1000; // one minute const WORKER_TIMEOUT = 60 * 1000; // one minute
@ -251,7 +254,7 @@ MessageReceiver.prototype.extend({
this.calledClose this.calledClose
); );
// TODO: handle properly // TODO: handle properly
return; return Promise.resolve();
this.shutdown(); this.shutdown();
if (this.calledClose) { if (this.calledClose) {
@ -708,7 +711,7 @@ MessageReceiver.prototype.extend({
promise = sessionCipher.decryptWhisperMessage(ciphertext) promise = sessionCipher.decryptWhisperMessage(ciphertext)
.then(this.unpad); .then(this.unpad);
break; break;
case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: {
window.log.info('friend-request message from ', envelope.source); window.log.info('friend-request message from ', envelope.source);
const fallBackSessionCipher = new libloki.FallBackSessionCipher( const fallBackSessionCipher = new libloki.FallBackSessionCipher(
address address
@ -716,6 +719,7 @@ MessageReceiver.prototype.extend({
promise = fallBackSessionCipher.decrypt(ciphertext.toArrayBuffer()) promise = fallBackSessionCipher.decrypt(ciphertext.toArrayBuffer())
.then(this.unpad); .then(this.unpad);
break; break;
}
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE: case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
window.log.info('prekey message from', this.getEnvelopeId(envelope)); window.log.info('prekey message from', this.getEnvelopeId(envelope));
promise = this.decryptPreKeyWhisperMessage( promise = this.decryptPreKeyWhisperMessage(
@ -971,13 +975,13 @@ MessageReceiver.prototype.extend({
if (!message || !message.direction || !message.friendStatus) return; if (!message || !message.direction || !message.friendStatus) return;
// Update the conversation // Update the conversation
const conversation = ConversationController.get(pubKey); const conversation = window.ConversationController.get(pubKey);
if (conversation) { if (conversation) {
// Update the conversation friend request indicator // Update the conversation friend request indicator
conversation.updatePendingFriendRequests(); conversation.updatePendingFriendRequests();
conversation.updateTextInputState(); conversation.updateTextInputState();
} }
// Send our own prekeys as a response // Send our own prekeys as a response
if (message.direction === 'incoming' && message.friendStatus === 'accepted') { if (message.direction === 'incoming' && message.friendStatus === 'accepted') {
libloki.sendEmptyMessageWithPreKeys(pubKey); libloki.sendEmptyMessageWithPreKeys(pubKey);
@ -990,9 +994,9 @@ MessageReceiver.prototype.extend({
); );
} }
await conversation.onFriendRequestAccepted(); await conversation.onFriendRequestAccepted({ updateUnread: false });
} }
console.log(`Friend request for ${pubKey} was ${message.friendStatus}`, message); window.log.info(`Friend request for ${pubKey} was ${message.friendStatus}`, message);
}, },
async innerHandleContentMessage(envelope, plaintext) { async innerHandleContentMessage(envelope, plaintext) {
const content = textsecure.protobuf.Content.decode(plaintext); const content = textsecure.protobuf.Content.decode(plaintext);
@ -1000,15 +1004,17 @@ MessageReceiver.prototype.extend({
if (envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) { if (envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) {
let conversation; let conversation;
try { try {
conversation = ConversationController.get(envelope.source); conversation = window.ConversationController.get(envelope.source);
} catch (e) { } } catch (e) {
throw new Error('Error getting conversation for message.')
}
// only prompt friend request if there is no conversation yet // only prompt friend request if there is no conversation yet
if (!conversation) { if (!conversation) {
this.promptUserToAcceptFriendRequest( this.promptUserToAcceptFriendRequest(
envelope, envelope,
content.dataMessage.body, content.dataMessage.body,
content.preKeyBundleMessage, content.preKeyBundleMessage
); );
} else { } else {
const keyExchangeComplete = conversation.isKeyExchangeCompleted(); const keyExchangeComplete = conversation.isKeyExchangeCompleted();
@ -1017,7 +1023,8 @@ MessageReceiver.prototype.extend({
// We are certain that other user accepted the friend request IF: // We are certain that other user accepted the friend request IF:
// - The message has a preKeyBundleMessage // - The message has a preKeyBundleMessage
// - We have an outgoing friend request that is pending // - We have an outgoing friend request that is pending
// The second check is crucial because it makes sure we don't save the preKeys of the incoming friend request (which is saved only when we press accept) // The second check is crucial because it makes sure we don't save the preKeys of
// the incoming friend request (which is saved only when we press accept)
if (!keyExchangeComplete && content.preKeyBundleMessage) { if (!keyExchangeComplete && content.preKeyBundleMessage) {
// Check for any outgoing friend requests // Check for any outgoing friend requests
const pending = await conversation.getPendingFriendRequests('outgoing'); const pending = await conversation.getPendingFriendRequests('outgoing');
@ -1040,7 +1047,7 @@ MessageReceiver.prototype.extend({
} }
// Exit early since the friend request reply will be a regular empty message // Exit early since the friend request reply will be a regular empty message
return; return Promise.resolve();
} }
if (content.syncMessage) { if (content.syncMessage) {
@ -1054,9 +1061,7 @@ MessageReceiver.prototype.extend({
} else if (content.receiptMessage) { } else if (content.receiptMessage) {
return this.handleReceiptMessage(envelope, content.receiptMessage); return this.handleReceiptMessage(envelope, content.receiptMessage);
} }
if (!content.preKeyBundleMessage) { throw new Error('Unsupported content message');
throw new Error('Unsupported content message');
}
}, },
handleCallMessage(envelope) { handleCallMessage(envelope) {
window.log.info('call message from', this.getEnvelopeId(envelope)); window.log.info('call message from', this.getEnvelopeId(envelope));
@ -1266,7 +1271,7 @@ MessageReceiver.prototype.extend({
preKeyBundleMessage.signature, preKeyBundleMessage.signature,
].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer()); ].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer());
return { return {
...preKeyBundleMessage, ...preKeyBundleMessage,
identityKey, identityKey,
preKey, preKey,
@ -1284,13 +1289,13 @@ MessageReceiver.prototype.extend({
signature, signature,
} = preKeyBundleMessage; } = preKeyBundleMessage;
if (pubKey != StringView.arrayBufferToHex(identityKey)) { if (pubKey !== StringView.arrayBufferToHex(identityKey)) {
throw new Error( throw new Error(
'Error in handlePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle' 'Error in handlePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle'
); );
} }
return await libloki.savePreKeyBundleForNumber({ return libloki.savePreKeyBundleForNumber({
pubKey, pubKey,
preKeyId, preKeyId,
signedKeyId, signedKeyId,
@ -1306,8 +1311,8 @@ MessageReceiver.prototype.extend({
return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0; return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0;
}, },
handleAttachment(attachment) { handleAttachment(attachment) {
console.log("Not handling attachments."); window.log.info('Not handling attachments.');
return; return Promise.reject();
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
attachment.id = attachment.id.toString(); attachment.id = attachment.id.toString();
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign

@ -7,11 +7,10 @@
StringView, StringView,
dcodeIO, dcodeIO,
log, log,
btoa,
_
*/ */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
/* eslint-disable no-unreachable */
function OutgoingMessage( function OutgoingMessage(
server, server,
@ -249,7 +248,8 @@ OutgoingMessage.prototype = {
if (accessKey && !senderCertificate) { if (accessKey && !senderCertificate) {
return Promise.reject( return Promise.reject(
new Error( new Error(
'OutgoingMessage.doSendMessage: accessKey was provided, but senderCertificate was not' 'OutgoingMessage.doSendMessage: accessKey was provided, ' +
'but senderCertificate was not'
) )
); );
} }

Loading…
Cancel
Save