Support for GV1 -> GV2 migration

This commit is contained in:
Scott Nonnenberg
2020-11-20 09:30:45 -08:00
committed by Josh Perez
parent a0baa3e03f
commit 2c69f2c367
32 changed files with 2626 additions and 341 deletions

View File

@@ -1,7 +1,12 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
type WhatIsThis = typeof window.WhatIsThis;
// This allows us to pull in types despite the fact that this is not a module. We can't
// use normal import syntax, nor can we use 'import type' syntax, or this will be turned
// into a module, and we'll get the dreaded 'exports is not defined' error.
// see https://github.com/microsoft/TypeScript/issues/41562
type DataMessageClass = import('./textsecure.d').DataMessageClass;
type WhatIsThis = import('./window.d').WhatIsThis;
// eslint-disable-next-line func-names
(async function () {
@@ -979,10 +984,6 @@ type WhatIsThis = typeof window.WhatIsThis;
if (className.includes('module-main-header__search__input')) {
return;
}
if (className.includes('module-contact-modal')) {
return;
}
}
// These add listeners to document, but we'll run first
@@ -1022,10 +1023,22 @@ type WhatIsThis = typeof window.WhatIsThis;
return;
}
const reactionPicker = document.querySelector('module-reaction-picker');
const reactionPicker = document.querySelector(
'.module-reaction-picker'
);
if (reactionPicker) {
return;
}
const contactModal = document.querySelector('.module-contact-modal');
if (contactModal) {
return;
}
const modalHost = document.querySelector('.module-modal-host__overlay');
if (modalHost) {
return;
}
}
// Close window.Backbone-based confirmation dialog
@@ -1975,22 +1988,21 @@ type WhatIsThis = typeof window.WhatIsThis;
}
}
// We need to do this after fetching our UUID
const hasRegisteredGV23Support = 'hasRegisteredGV23Support';
if (
!window.storage.get(hasRegisteredGV23Support) &&
window.textsecure.storage.user.getUuid()
) {
if (connectCount === 1) {
const server = window.WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerCapabilities({ 'gv2-3': true });
window.storage.put(hasRegisteredGV23Support, true);
// Note: we always have to register our capabilities all at once, so we do this
// after connect on every startup
await server.registerCapabilities({
'gv2-3': true,
'gv1-migration': true,
});
} catch (error) {
window.log.error(
'Error: Unable to register support for GV2.',
'Error: Unable to register our capabilities.',
error && error.stack ? error.stack : error
);
}
@@ -2227,16 +2239,35 @@ type WhatIsThis = typeof window.WhatIsThis;
return;
}
let conversation;
const senderId = window.ConversationController.ensureContactIds({
e164: sender,
uuid: senderUuid,
highTrust: true,
});
const conversation = window.ConversationController.get(
groupV2Id || groupId || senderId
);
// We multiplex between GV1/GV2 groups here, but we don't kick off migrations
if (groupV2Id) {
conversation = window.ConversationController.get(groupV2Id);
}
if (!conversation && groupId) {
conversation = window.ConversationController.get(groupId);
}
if (!groupV2Id && !groupId && senderId) {
conversation = window.ConversationController.get(senderId);
}
const ourId = window.ConversationController.getOurConversationId();
if (!senderId) {
window.log.warn('onTyping: ensureContactIds returned falsey senderId!');
return;
}
if (!ourId) {
window.log.warn("onTyping: Couldn't get our own id!");
return;
}
if (!conversation) {
window.log.warn(
`onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), group(${groupId}), ${sender}, ${senderUuid})`
@@ -2245,8 +2276,7 @@ type WhatIsThis = typeof window.WhatIsThis;
}
// We drop typing notifications in groups we're not a part of
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (!conversation.isPrivate() && !conversation.hasMember(ourId!)) {
if (!conversation.isPrivate() && !conversation.hasMember(ourId)) {
window.log.warn(
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
);
@@ -2255,12 +2285,10 @@ type WhatIsThis = typeof window.WhatIsThis;
conversation.notifyTyping({
isTyping: started,
isMe: ourId === senderId,
sender,
senderUuid,
fromMe: senderId === ourId,
senderId,
senderDevice,
} as WhatIsThis);
});
}
async function onStickerPack(ev: WhatIsThis) {
@@ -2552,64 +2580,18 @@ type WhatIsThis = typeof window.WhatIsThis;
return confirm();
}
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
const getDescriptorForReceived = ({
message,
source,
sourceUuid,
}: WhatIsThis) => {
if (message.groupV2) {
const { id } = message.groupV2;
const conversationId = window.ConversationController.ensureGroup(id, {
// Note: We don't set active_at, because we don't want the group to show until
// we have information about it beyond these initial details.
// see maybeUpdateGroup().
groupVersion: 2,
masterKey: message.groupV2.masterKey,
secretParams: message.groupV2.secretParams,
publicParams: message.groupV2.publicParams,
});
return {
type: Message.GROUP,
id: conversationId,
};
}
if (message.group) {
const { id } = message.group;
const fromContactId = window.ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
highTrust: true,
});
const conversationId = window.ConversationController.ensureGroup(id, {
addedBy: fromContactId,
});
return {
type: Message.GROUP,
id: conversationId,
};
}
return {
type: Message.PRIVATE,
id: window.ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
highTrust: true,
}),
};
};
// Note: We do very little in this function, since everything in handleDataMessage is
// inside a conversation-specific queue(). Any code here might run before an earlier
// message is processed in handleDataMessage().
function onMessageReceived(event: WhatIsThis) {
const { data, confirm } = event;
const messageDescriptor = getDescriptorForReceived(data);
const messageDescriptor = getMessageDescriptor({
...data,
// 'message' event: for 1:1 converations, the conversation is same as sender
destination: data.source,
destinationUuid: data.sourceUuid,
});
const { PROFILE_KEY_UPDATE } = window.textsecure.protobuf.DataMessage.Flags;
// eslint-disable-next-line no-bitwise
@@ -2776,15 +2758,50 @@ type WhatIsThis = typeof window.WhatIsThis;
} as WhatIsThis);
}
// Matches event data from `libtextsecure` `MessageReceiver::handleSentMessage`:
const getDescriptorForSent = ({
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
// at callsites to make sure both source and destination are populated.
const getMessageDescriptor = ({
message,
source,
sourceUuid,
destination,
destinationUuid,
}: WhatIsThis) => {
}: {
message: DataMessageClass;
source: string;
sourceUuid: string;
destination: string;
destinationUuid: string;
}): MessageDescriptor => {
if (message.groupV2) {
const { id } = message.groupV2;
if (!id) {
throw new Error('getMessageDescriptor: GroupV2 data was missing an id');
}
// First we check for an existing GroupV2 group
const groupV2 = window.ConversationController.get(id);
if (groupV2) {
return {
type: Message.GROUP,
id: groupV2.id,
};
}
// Then check for V1 group with matching derived GV2 id
const groupV1 = window.ConversationController.getByDerivedGroupV2Id(id);
if (groupV1) {
return {
type: Message.GROUP,
id: groupV1.id,
};
}
// Finally create the V2 group normally
const conversationId = window.ConversationController.ensureGroup(id, {
// Note: We don't set active_at, because we don't want the group to show until
// we have information about it beyond these initial details.
// see maybeUpdateGroup().
groupVersion: 2,
masterKey: message.groupV2.masterKey,
secretParams: message.groupV2.secretParams,
@@ -2797,8 +2814,37 @@ type WhatIsThis = typeof window.WhatIsThis;
};
}
if (message.group) {
const { id } = message.group;
const conversationId = window.ConversationController.ensureGroup(id);
const { id, derivedGroupV2Id } = message.group;
if (!id) {
throw new Error('getMessageDescriptor: GroupV1 data was missing id');
}
if (!derivedGroupV2Id) {
window.log.warn(
'getMessageDescriptor: GroupV1 data was missing derivedGroupV2Id'
);
} else {
// First we check for an already-migrated GroupV2 group
const migratedGroup = window.ConversationController.get(
derivedGroupV2Id
);
if (migratedGroup) {
return {
type: Message.GROUP,
id: migratedGroup.id,
};
}
}
// If we can't find one, we treat this as a normal GroupV1 group
const fromContactId = window.ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
highTrust: true,
});
const conversationId = window.ConversationController.ensureGroup(id, {
addedBy: fromContactId,
});
return {
type: Message.GROUP,
@@ -2806,13 +2852,20 @@ type WhatIsThis = typeof window.WhatIsThis;
};
}
const id = window.ConversationController.ensureContactIds({
e164: destination,
uuid: destinationUuid,
highTrust: true,
});
if (!id) {
throw new Error(
'getMessageDescriptor: ensureContactIds returned falsey id'
);
}
return {
type: Message.PRIVATE,
id: window.ConversationController.ensureContactIds({
e164: destination,
uuid: destinationUuid,
highTrust: true,
}),
id,
};
};
@@ -2822,7 +2875,12 @@ type WhatIsThis = typeof window.WhatIsThis;
function onSentMessage(event: WhatIsThis) {
const { data, confirm } = event;
const messageDescriptor = getDescriptorForSent(data);
const messageDescriptor = getMessageDescriptor({
...data,
// 'sent' event: the sender is always us!
source: window.textsecure.storage.user.getNumber(),
sourceUuid: window.textsecure.storage.user.getUuid(),
});
const { PROFILE_KEY_UPDATE } = window.textsecure.protobuf.DataMessage.Flags;
// eslint-disable-next-line no-bitwise
@@ -2885,7 +2943,15 @@ type WhatIsThis = typeof window.WhatIsThis;
return Promise.resolve();
}
function initIncomingMessage(data: WhatIsThis, descriptor: WhatIsThis) {
type MessageDescriptor = {
type: 'private' | 'group';
id: string;
};
function initIncomingMessage(
data: WhatIsThis,
descriptor: MessageDescriptor
) {
return new window.Whisper.Message({
source: data.source,
sourceUuid: data.sourceUuid,
@@ -2998,12 +3064,16 @@ type WhatIsThis = typeof window.WhatIsThis;
return Promise.resolve();
}
const envelope = ev.proto;
const id = window.ConversationController.ensureContactIds({
e164: envelope.source,
uuid: envelope.sourceUuid,
});
if (!id) {
throw new Error('onError: ensureContactIds returned falsey id!');
}
const message = initIncomingMessage(envelope, {
type: Message.PRIVATE,
id: window.ConversationController.ensureContactIds({
e164: envelope.source,
uuid: envelope.sourceUuid,
}),
id,
});
const conversationId = message.get('conversationId');
@@ -3141,18 +3211,29 @@ type WhatIsThis = typeof window.WhatIsThis;
async function onMessageRequestResponse(ev: WhatIsThis) {
ev.confirm();
const { threadE164, threadUuid, groupId, messageRequestResponseType } = ev;
const args = {
const {
threadE164,
threadUuid,
groupId,
groupV2Id,
messageRequestResponseType,
} = ev;
window.log.info('onMessageRequestResponse', {
threadE164,
threadUuid,
groupId: `group(${groupId})`,
groupV2Id: `groupv2(${groupV2Id})`,
messageRequestResponseType,
});
const sync = window.Whisper.MessageRequests.add({
threadE164,
threadUuid,
groupId,
groupV2Id,
type: messageRequestResponseType,
};
window.log.info('message request response', args);
const sync = window.Whisper.MessageRequests.add(args);
});
window.Whisper.MessageRequests.onResponse(sync);
}