Migrate to SQLCipher for messages/cache

Quite a few other fixes, including:
  - Sending to contact with no avatar yet (not synced from mobile)
  - Left pane doesn't update quickly or at all on new message
  - Left pane doesn't show sent or error status

Also:
 - Contributing.md: Ensure set of linux dev dependencies is complete
This commit is contained in:
Scott Nonnenberg 2018-07-26 18:13:56 -07:00
parent fc461c82ce
commit 3105b77475
29 changed files with 2006 additions and 716 deletions

View file

@ -116,15 +116,21 @@
const debouncedUpdateLastMessage = _.debounce(
this.updateLastMessage.bind(this),
1000
200
);
this.listenTo(
this.messageCollection,
'add remove',
'add remove destroy',
debouncedUpdateLastMessage
);
this.listenTo(this.model, 'newmessage', debouncedUpdateLastMessage);
this.listenTo(this.messageCollection, 'sent', this.updateLastMessage);
this.listenTo(
this.messageCollection,
'send-error',
this.updateLastMessage
);
this.on('newmessage', this.updateLastMessage);
this.on('change:avatar', this.updateAvatarUrl);
this.on('change:profileAvatar', this.updateAvatarUrl);
this.on('change:profileKey', this.onChangeProfileKey);
@ -133,10 +139,7 @@
// Listening for out-of-band data updates
this.on('delivered', this.updateAndMerge);
this.on('read', this.updateAndMerge);
this.on('sent', this.updateLastMessage);
this.on('expired', this.onExpired);
this.updateLastMessage();
},
isMe() {
@ -378,98 +381,6 @@
return Promise.all(promises).then(() => lookup);
},
replay(error, message) {
const replayable = new textsecure.ReplayableError(error);
return replayable.replay(message.attributes).catch(e => {
window.log.error('replay error:', e && e.stack ? e.stack : e);
});
},
decryptOldIncomingKeyErrors() {
// We want to run just once per conversation
if (this.get('decryptedOldIncomingKeyErrors')) {
return Promise.resolve();
}
window.log.info(
'decryptOldIncomingKeyErrors start for',
this.idForLogging()
);
const messages = this.messageCollection.filter(message => {
const errors = message.get('errors');
if (!errors || !errors[0]) {
return false;
}
const error = _.find(
errors,
e => e.name === 'IncomingIdentityKeyError'
);
return Boolean(error);
});
const markComplete = () => {
window.log.info(
'decryptOldIncomingKeyErrors complete for',
this.idForLogging()
);
return new Promise(resolve => {
this.save({ decryptedOldIncomingKeyErrors: true }).always(resolve);
});
};
if (!messages.length) {
return markComplete();
}
window.log.info(
'decryptOldIncomingKeyErrors found',
messages.length,
'messages to process'
);
const safeDelete = async message => {
try {
window.Signal.Data.removeMessage(message.id, {
Message: Whisper.Message,
});
} catch (error) {
// nothing
}
};
const promise = this.getIdentityKeys();
return promise
.then(lookup =>
Promise.all(
_.map(messages, message => {
const source = message.get('source');
const error = _.find(
message.get('errors'),
e => e.name === 'IncomingIdentityKeyError'
);
const key = lookup[source];
if (!key) {
return Promise.resolve();
}
if (constantTimeEqualArrayBuffers(key, error.identityKey)) {
return this.replay(error, message).then(() =>
safeDelete(message)
);
}
return Promise.resolve();
})
)
)
.catch(error => {
window.log.error(
'decryptOldIncomingKeyErrors error:',
error && error.stack ? error.stack : error
);
})
.then(markComplete);
},
isVerified() {
if (this.isPrivate()) {
return this.get('verified') === this.verifiedEnum.VERIFIED;
@ -926,12 +837,8 @@
this.id,
{ limit: 1, MessageCollection: Whisper.MessageCollection }
);
if (!messages.length) {
return;
}
const lastMessageModel = messages.at(0);
const lastMessageJSON = lastMessageModel
? lastMessageModel.toJSON()
: null;
@ -968,7 +875,7 @@
}
},
updateExpirationTimer(
async updateExpirationTimer(
providedExpireTimer,
providedSource,
receivedAt,
@ -1024,46 +931,48 @@
message.set({ recipients: this.getRecipients() });
}
return Promise.all([
window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
}),
wrapDeferred(this.save({ expireTimer })),
]).then(() => {
// if change was made remotely, don't send it to the number/group
if (receivedAt) {
return message;
}
let sendFunc;
if (this.get('type') === 'private') {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber;
} else {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToGroup;
}
let profileKey;
if (this.get('profileSharing')) {
profileKey = storage.get('profileKey');
}
const promise = sendFunc(
this.get('id'),
this.get('expireTimer'),
message.get('sent_at'),
profileKey
);
return message.send(promise).then(() => message);
const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
});
message.set({ id });
await wrapDeferred(this.save({ expireTimer }));
// if change was made remotely, don't send it to the number/group
if (receivedAt) {
return message;
}
let sendFunc;
if (this.get('type') === 'private') {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber;
} else {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToGroup;
}
let profileKey;
if (this.get('profileSharing')) {
profileKey = storage.get('profileKey');
}
const promise = sendFunc(
this.get('id'),
this.get('expireTimer'),
message.get('sent_at'),
profileKey
);
await message.send(promise);
return message;
},
isSearchable() {
return !this.get('left') || !!this.get('lastMessage');
},
endSession() {
async endSession() {
if (this.isPrivate()) {
const now = Date.now();
const message = this.messageCollection.create({
const message = this.messageCollection.add({
conversationId: this.id,
type: 'outgoing',
sent_at: now,
@ -1072,11 +981,17 @@
recipients: this.getRecipients(),
flags: textsecure.protobuf.DataMessage.Flags.END_SESSION,
});
const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
});
message.set({ id });
message.send(textsecure.messaging.resetSession(this.id, now));
}
},
updateGroup(providedGroupUpdate) {
async updateGroup(providedGroupUpdate) {
let groupUpdate = providedGroupUpdate;
if (this.isPrivate()) {
@ -1086,13 +1001,19 @@
groupUpdate = this.pick(['name', 'avatar', 'members']);
}
const now = Date.now();
const message = this.messageCollection.create({
const message = this.messageCollection.add({
conversationId: this.id,
type: 'outgoing',
sent_at: now,
received_at: now,
group_update: groupUpdate,
});
const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
});
message.set({ id });
message.send(
textsecure.messaging.updateGroup(
this.id,
@ -1103,17 +1024,23 @@
);
},
leaveGroup() {
async leaveGroup() {
const now = Date.now();
if (this.get('type') === 'group') {
this.save({ left: true });
const message = this.messageCollection.create({
const message = this.messageCollection.add({
group_update: { left: 'You' },
conversationId: this.id,
type: 'outgoing',
sent_at: now,
received_at: now,
});
const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
});
message.set({ id });
message.send(textsecure.messaging.leaveGroup(this.id));
}
},