Passive UUID support

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
Ken Powers 2020-03-05 13:14:58 -08:00 committed by Scott Nonnenberg
parent f64ca0ed21
commit a90246cbe5
49 changed files with 2226 additions and 776 deletions

View file

@ -25,7 +25,7 @@
wait: 500,
maxSize: 500,
processBatch: async items => {
const bySource = _.groupBy(items, item => item.source);
const bySource = _.groupBy(items, item => item.source || item.sourceUuid);
const sources = Object.keys(bySource);
for (let i = 0, max = sources.length; i < max; i += 1) {
@ -33,13 +33,15 @@
const timestamps = bySource[source].map(item => item.timestamp);
try {
const c = ConversationController.get(source);
const { wrap, sendOptions } = ConversationController.prepareForSend(
source
c.get('id')
);
// eslint-disable-next-line no-await-in-loop
await wrap(
textsecure.messaging.sendDeliveryReceipt(
source,
c.get('e164'),
c.get('uuid'),
timestamps,
sendOptions
)
@ -234,13 +236,23 @@
let accountManager;
window.getAccountManager = () => {
if (!accountManager) {
const USERNAME = storage.get('number_id');
const OLD_USERNAME = storage.get('number_id');
const USERNAME = storage.get('uuid_id');
const PASSWORD = storage.get('password');
accountManager = new textsecure.AccountManager(USERNAME, PASSWORD);
accountManager = new textsecure.AccountManager(
USERNAME || OLD_USERNAME,
PASSWORD
);
accountManager.addEventListener('registration', () => {
const ourNumber = textsecure.storage.user.getNumber();
const ourUuid = textsecure.storage.user.getUuid();
const user = {
regionCode: window.storage.get('regionCode'),
ourNumber: textsecure.storage.user.getNumber(),
ourNumber,
ourUuid,
ourConversationId: ConversationController.getConversationId(
ourNumber || ourUuid
),
};
Whisper.events.trigger('userChanged', user);
@ -580,6 +592,11 @@
const conversations = convoCollection.map(
conversation => conversation.cachedProps
);
const ourNumber = textsecure.storage.user.getNumber();
const ourUuid = textsecure.storage.user.getUuid();
const ourConversationId = ConversationController.getConversationId(
ourNumber || ourUuid
);
const initialState = {
conversations: {
conversationLookup: Signal.Util.makeLookup(conversations, 'id'),
@ -598,7 +615,9 @@
stickersPath: window.baseStickersPath,
tempPath: window.baseTempPath,
regionCode: window.storage.get('regionCode'),
ourNumber: textsecure.storage.user.getNumber(),
ourConversationId,
ourNumber,
ourUuid,
platform: window.platform,
i18n: window.i18n,
interactionMode: window.getInteractionMode(),
@ -1508,7 +1527,8 @@
messageReceiver = null;
}
const USERNAME = storage.get('number_id');
const OLD_USERNAME = storage.get('number_id');
const USERNAME = storage.get('uuid_id');
const PASSWORD = storage.get('password');
const mySignalingKey = storage.get('signaling_key');
@ -1524,6 +1544,7 @@
// initialize the socket and start listening for messages
window.log.info('Initializing socket and listening for messages');
messageReceiver = new textsecure.MessageReceiver(
OLD_USERNAME,
USERNAME,
PASSWORD,
mySignalingKey,
@ -1569,7 +1590,7 @@
});
window.textsecure.messaging = new textsecure.MessageSender(
USERNAME,
USERNAME || OLD_USERNAME,
PASSWORD
);
@ -1605,7 +1626,10 @@
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
if (!storage.get(udSupportKey)) {
const server = WebAPI.connect({ username: USERNAME, password: PASSWORD });
const server = WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerSupportForUnauthenticatedDelivery();
storage.put(udSupportKey, true);
@ -1617,7 +1641,50 @@
}
}
const hasRegisteredUuidSupportKey = 'hasRegisteredUuidSupport';
if (
!storage.get(hasRegisteredUuidSupportKey) &&
textsecure.storage.user.getUuid()
) {
const server = WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerCapabilities({ uuid: true });
storage.put(hasRegisteredUuidSupportKey, true);
} catch (error) {
window.log.error(
'Error: Unable to register support for UUID messages.',
error && error.stack ? error.stack : error
);
}
}
const deviceId = textsecure.storage.user.getDeviceId();
if (!textsecure.storage.user.getUuid()) {
const server = WebAPI.connect({
username: OLD_USERNAME,
password: PASSWORD,
});
try {
const { uuid } = await server.whoami();
textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
const ourNumber = textsecure.storage.user.getNumber();
const me = await ConversationController.getOrCreateAndWait(
ourNumber,
'private'
);
me.updateUuid(uuid);
} catch (error) {
window.log.error(
'Error: Unable to retrieve UUID from service.',
error && error.stack ? error.stack : error
);
}
}
if (firstRun === true && deviceId !== '1') {
const hasThemeSetting = Boolean(storage.get('theme-setting'));
if (!hasThemeSetting && textsecure.storage.get('userAgent') === 'OWI') {
@ -1639,9 +1706,10 @@
Whisper.events.trigger('contactsync');
});
const ourUuid = textsecure.storage.user.getUuid();
const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend(
ourNumber,
ourNumber || ourUuid,
{
syncMessage: true,
}
@ -1766,7 +1834,7 @@
function onTyping(ev) {
// Note: this type of message is automatically removed from cache in MessageReceiver
const { typing, sender, senderDevice } = ev;
const { typing, sender, senderUuid, senderDevice } = ev;
const { groupId, started } = typing || {};
// We don't do anything with incoming typing messages if the setting is disabled
@ -1774,12 +1842,18 @@
return;
}
const conversation = ConversationController.get(groupId || sender);
const conversation = ConversationController.get(
groupId || sender || senderUuid
);
const ourUuid = textsecure.storage.user.getUuid();
const ourNumber = textsecure.storage.user.getNumber();
if (conversation) {
// We drop typing notifications in groups we're not a part of
if (!conversation.isPrivate() && !conversation.hasMember(ourNumber)) {
if (
!conversation.isPrivate() &&
!conversation.hasMember(ourNumber || ourUuid)
) {
window.log.warn(
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
);
@ -1789,6 +1863,7 @@
conversation.notifyTyping({
isTyping: started,
sender,
senderUuid,
senderDevice,
});
}
@ -1833,9 +1908,10 @@
async function onContactReceived(ev) {
const details = ev.contactDetails;
const id = details.number;
if (id === textsecure.storage.user.getNumber()) {
if (
details.number === textsecure.storage.user.getNumber() ||
details.uuid === textsecure.storage.user.getUuid()
) {
// special case for syncing details about ourselves
if (details.profileKey) {
window.log.info('Got sync message with our own profile key');
@ -1844,9 +1920,11 @@
}
const c = new Whisper.Conversation({
id,
e164: details.number,
uuid: details.uuid,
type: 'private',
});
const validationError = c.validateNumber();
const validationError = c.validate();
if (validationError) {
window.log.error(
'Invalid contact received:',
@ -1857,7 +1935,7 @@
try {
const conversation = await ConversationController.getOrCreateAndWait(
id,
details.number || details.uuid,
'private'
);
let activeAt = conversation.get('active_at');
@ -1878,10 +1956,18 @@
}
if (typeof details.blocked !== 'undefined') {
if (details.blocked) {
storage.addBlockedNumber(id);
const e164 = conversation.get('e164');
if (details.blocked && e164) {
storage.addBlockedNumber(e164);
} else {
storage.removeBlockedNumber(id);
storage.removeBlockedNumber(e164);
}
const uuid = conversation.get('uuid');
if (details.blocked && uuid) {
storage.addBlockedUuid(uuid);
} else {
storage.removeBlockedUuid(uuid);
}
}
@ -1912,17 +1998,21 @@
conversation.set({ avatar: null });
}
window.Signal.Data.updateConversation(id, conversation.attributes);
window.Signal.Data.updateConversation(
details.number || details.uuid,
conversation.attributes
);
const { expireTimer } = details;
const isValidExpireTimer = typeof expireTimer === 'number';
if (isValidExpireTimer) {
const source = textsecure.storage.user.getNumber();
const sourceE164 = textsecure.storage.user.getNumber();
const sourceUuid = textsecure.storage.user.getUuid();
const receivedAt = Date.now();
await conversation.updateExpirationTimer(
expireTimer,
source,
sourceE164 || sourceUuid,
receivedAt,
{ fromSync: true }
);
@ -1934,6 +2024,7 @@
verifiedEvent.verified = {
state: verified.state,
destination: verified.destination,
destinationUuid: verified.destinationUuid,
identityKey: verified.identityKey.toArrayBuffer(),
};
verifiedEvent.viaContactSync = true;
@ -1953,9 +2044,23 @@
'group'
);
const memberConversations = await Promise.all(
(details.members || details.membersE164).map(member => {
if (member.e164 || member.uuid) {
return ConversationController.getOrCreateAndWait(
member.e164 || member.uuid,
'private'
);
}
return ConversationController.getOrCreateAndWait(member, 'private');
})
);
const members = memberConversations.map(c => c.get('id'));
const updates = {
name: details.name,
members: details.members,
members,
color: details.color,
type: 'group',
};
@ -2004,11 +2109,17 @@
return;
}
const source = textsecure.storage.user.getNumber();
const sourceE164 = textsecure.storage.user.getNumber();
const sourceUuid = textsecure.storage.user.getUuid();
const receivedAt = Date.now();
await conversation.updateExpirationTimer(expireTimer, source, receivedAt, {
fromSync: true,
});
await conversation.updateExpirationTimer(
expireTimer,
sourceE164 || sourceUuid,
receivedAt,
{
fromSync: true,
}
);
}
// Descriptors
@ -2024,10 +2135,10 @@
: { type: Message.PRIVATE, id: destination };
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
const getDescriptorForReceived = ({ message, source }) =>
const getDescriptorForReceived = ({ message, source, sourceUuid }) =>
message.group
? getGroupDescriptor(message.group)
: { type: Message.PRIVATE, id: source };
: { type: Message.PRIVATE, id: source || sourceUuid };
// Received:
async function handleMessageReceivedProfileUpdate({
@ -2069,11 +2180,18 @@
const message = await initIncomingMessage(data);
await ConversationController.getOrCreateAndWait(
const result = await ConversationController.getOrCreateAndWait(
messageDescriptor.id,
messageDescriptor.type
);
if (messageDescriptor.type === 'private') {
result.updateE164(data.source);
if (data.sourceUuid) {
result.updateUuid(data.sourceUuid);
}
}
if (data.message.reaction) {
const { reaction } = data.message;
const reactionModel = Whisper.Reactions.add({
@ -2083,7 +2201,7 @@
targetAuthorUuid: reaction.targetAuthorUuid,
targetTimestamp: reaction.targetTimestamp.toNumber(),
timestamp: Date.now(),
fromId: data.source,
fromId: data.source || data.sourceUuid,
});
// Note: We do not wait for completion here
Whisper.Reactions.onReaction(reactionModel);
@ -2112,8 +2230,12 @@
// Then we update our own profileKey if it's different from what we have
const ourNumber = textsecure.storage.user.getNumber();
const ourUuid = textsecure.storage.user.getUuid();
const profileKey = data.message.profileKey.toString('base64');
const me = await ConversationController.getOrCreate(ourNumber, 'private');
const me = await ConversationController.getOrCreate(
ourNumber || ourUuid,
'private'
);
// Will do the save for us if needed
await me.setProfileKey(profileKey);
@ -2136,6 +2258,7 @@
return new Whisper.Message({
source: textsecure.storage.user.getNumber(),
sourceUuid: textsecure.storage.user.getUuid(),
sourceDevice: data.device,
sent_at: data.timestamp,
sent_to: sentTo,
@ -2176,6 +2299,7 @@
if (data.message.reaction) {
const { reaction } = data.message;
const ourNumber = textsecure.storage.user.getNumber();
const ourUuid = textsecure.storage.user.getUuid();
const reactionModel = Whisper.Reactions.add({
emoji: reaction.emoji,
remove: reaction.remove,
@ -2183,7 +2307,7 @@
targetAuthorUuid: reaction.targetAuthorUuid,
targetTimestamp: reaction.targetTimestamp.toNumber(),
timestamp: Date.now(),
fromId: ourNumber,
fromId: ourNumber || ourUuid,
fromSync: true,
});
// Note: We do not wait for completion here
@ -2197,20 +2321,25 @@
messageDescriptor.id,
messageDescriptor.type
);
// Don't wait for handleDataMessage, as it has its own per-conversation queueing
message.handleDataMessage(data.message, event.confirm, {
data,
});
}
async function initIncomingMessage(data) {
const targetId = data.source || data.sourceUuid;
const conversation = ConversationController.get(targetId);
const conversationId = conversation ? conversation.id : targetId;
return new Whisper.Message({
source: data.source,
sourceUuid: data.sourceUuid,
sourceDevice: data.sourceDevice,
sent_at: data.timestamp,
received_at: data.receivedAt || Date.now(),
conversationId: data.source,
conversationId,
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
type: 'incoming',
unread: 1,
@ -2384,11 +2513,16 @@
async function onViewSync(ev) {
ev.confirm();
const { source, timestamp } = ev;
const { source, sourceUuid, timestamp } = ev;
window.log.info(`view sync ${source} ${timestamp}`);
const conversationId = ConversationController.getConversationId(
source || sourceUuid
);
const sync = Whisper.ViewSyncs.add({
source,
sourceUuid,
conversationId,
timestamp,
});
@ -2398,12 +2532,12 @@
function onReadReceipt(ev) {
const readAt = ev.timestamp;
const { timestamp } = ev.read;
const { reader } = ev.read;
const reader = ConversationController.getConversationId(ev.read.reader);
window.log.info('read receipt', reader, timestamp);
ev.confirm();
if (!storage.get('read-receipt-setting')) {
if (!storage.get('read-receipt-setting') || !reader) {
return;
}
@ -2420,11 +2554,12 @@
function onReadSync(ev) {
const readAt = ev.timestamp;
const { timestamp } = ev.read;
const { sender } = ev.read;
window.log.info('read sync', sender, timestamp);
const { sender, senderUuid } = ev.read;
window.log.info('read sync', sender, senderUuid, timestamp);
const receipt = Whisper.ReadSyncs.add({
sender,
senderUuid,
timestamp,
read_at: readAt,
});
@ -2437,7 +2572,8 @@
}
async function onVerified(ev) {
const number = ev.verified.destination;
const e164 = ev.verified.destination;
const uuid = ev.verified.destinationUuid;
const key = ev.verified.identityKey;
let state;
@ -2446,12 +2582,16 @@
}
const c = new Whisper.Conversation({
id: number,
e164,
uuid,
type: 'private',
});
const error = c.validateNumber();
const error = c.validate();
if (error) {
window.log.error(
'Invalid verified sync received:',
e164,
uuid,
Errors.toLogFormat(error)
);
return;
@ -2473,13 +2613,14 @@
window.log.info(
'got verified sync for',
number,
e164,
uuid,
state,
ev.viaContactSync ? 'via contact sync' : ''
);
const contact = await ConversationController.getOrCreateAndWait(
number,
e164 || uuid,
'private'
);
const options = {
@ -2499,17 +2640,27 @@
function onDeliveryReceipt(ev) {
const { deliveryReceipt } = ev;
const { sourceUuid, source } = deliveryReceipt;
const identifier = source || sourceUuid;
window.log.info(
'delivery receipt from',
`${deliveryReceipt.source}.${deliveryReceipt.sourceDevice}`,
`${identifier}.${deliveryReceipt.sourceDevice}`,
deliveryReceipt.timestamp
);
ev.confirm();
const deliveredTo = ConversationController.getConversationId(identifier);
if (!deliveredTo) {
window.log.info('no conversation for identifier', identifier);
return;
}
const receipt = Whisper.DeliveryReceipts.add({
timestamp: deliveryReceipt.timestamp,
source: deliveryReceipt.source,
deliveredTo,
});
// Note: We don't wait for completion here