Passive UUID support
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
f64ca0ed21
commit
a90246cbe5
49 changed files with 2226 additions and 776 deletions
253
js/background.js
253
js/background.js
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue