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
|
@ -24,14 +24,14 @@
|
|||
this.pending = Promise.resolve();
|
||||
}
|
||||
|
||||
function getNumber(numberId) {
|
||||
if (!numberId || !numberId.length) {
|
||||
return numberId;
|
||||
function getIdentifier(id) {
|
||||
if (!id || !id.length) {
|
||||
return id;
|
||||
}
|
||||
|
||||
const parts = numberId.split('.');
|
||||
const parts = id.split('.');
|
||||
if (!parts.length) {
|
||||
return numberId;
|
||||
return id;
|
||||
}
|
||||
|
||||
return parts[0];
|
||||
|
@ -136,7 +136,7 @@
|
|||
.then(clearSessionsAndPreKeys)
|
||||
.then(generateKeys)
|
||||
.then(keys => registerKeys(keys).then(() => confirmKeys(keys)))
|
||||
.then(() => registrationDone(number));
|
||||
.then(() => registrationDone({ number }));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -212,7 +212,8 @@
|
|||
provisionMessage.profileKey,
|
||||
deviceName,
|
||||
provisionMessage.userAgent,
|
||||
provisionMessage.readReceipts
|
||||
provisionMessage.readReceipts,
|
||||
{ uuid: provisionMessage.uuid }
|
||||
)
|
||||
.then(clearSessionsAndPreKeys)
|
||||
.then(generateKeys)
|
||||
|
@ -221,9 +222,7 @@
|
|||
confirmKeys(keys)
|
||||
)
|
||||
)
|
||||
.then(() =>
|
||||
registrationDone(provisionMessage.number)
|
||||
);
|
||||
.then(() => registrationDone(provisionMessage));
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -414,7 +413,8 @@
|
|||
password = password.substring(0, password.length - 2);
|
||||
const registrationId = libsignal.KeyHelper.generateRegistrationId();
|
||||
|
||||
const previousNumber = getNumber(textsecure.storage.get('number_id'));
|
||||
const previousNumber = getIdentifier(textsecure.storage.get('number_id'));
|
||||
const previousUuid = getIdentifier(textsecure.storage.get('uuid_id'));
|
||||
|
||||
const encryptedDeviceName = await this.encryptDeviceName(
|
||||
deviceName,
|
||||
|
@ -437,10 +437,21 @@
|
|||
{ accessKey }
|
||||
);
|
||||
|
||||
if (previousNumber && previousNumber !== number) {
|
||||
window.log.warn(
|
||||
'New number is different from old number; deleting all previous data'
|
||||
);
|
||||
const numberChanged = previousNumber && previousNumber !== number;
|
||||
const uuidChanged =
|
||||
previousUuid && response.uuid && previousUuid !== response.uuid;
|
||||
|
||||
if (numberChanged || uuidChanged) {
|
||||
if (numberChanged) {
|
||||
window.log.warn(
|
||||
'New number is different from old number; deleting all previous data'
|
||||
);
|
||||
}
|
||||
if (uuidChanged) {
|
||||
window.log.warn(
|
||||
'New uuid is different from old uuid; deleting all previous data'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await textsecure.storage.protocol.removeAllData();
|
||||
|
@ -465,10 +476,29 @@
|
|||
textsecure.storage.remove('read-receipts-setting'),
|
||||
]);
|
||||
|
||||
// `setNumberAndDeviceId` and `setUuidAndDeviceId` need to be called
|
||||
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||
// indirectly calls `ConversationController.getConverationId()` which
|
||||
// initializes the conversation for the given number (our number) which
|
||||
// calls out to the user storage API to get the stored UUID and number
|
||||
// information.
|
||||
await textsecure.storage.user.setNumberAndDeviceId(
|
||||
number,
|
||||
response.deviceId || 1,
|
||||
deviceName
|
||||
);
|
||||
|
||||
const setUuid = response.uuid;
|
||||
if (setUuid) {
|
||||
await textsecure.storage.user.setUuidAndDeviceId(
|
||||
setUuid,
|
||||
response.deviceId || 1
|
||||
);
|
||||
}
|
||||
|
||||
// update our own identity key, which may have changed
|
||||
// if we're relinking after a reinstall on the master device
|
||||
await textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
||||
id: number,
|
||||
publicKey: identityKeyPair.pubKey,
|
||||
firstUse: true,
|
||||
timestamp: Date.now(),
|
||||
|
@ -491,12 +521,6 @@
|
|||
Boolean(readReceipts)
|
||||
);
|
||||
|
||||
await textsecure.storage.user.setNumberAndDeviceId(
|
||||
number,
|
||||
response.deviceId || 1,
|
||||
deviceName
|
||||
);
|
||||
|
||||
const regionCode = libphonenumber.util.getRegionCodeForNumber(number);
|
||||
await textsecure.storage.put('regionCode', regionCode);
|
||||
await textsecure.storage.protocol.hydrateCaches();
|
||||
|
@ -579,11 +603,16 @@
|
|||
);
|
||||
});
|
||||
},
|
||||
async registrationDone(number) {
|
||||
async registrationDone({ uuid, number }) {
|
||||
window.log.info('registration done');
|
||||
|
||||
// Ensure that we always have a conversation for ourself
|
||||
await ConversationController.getOrCreateAndWait(number, 'private');
|
||||
const conversation = await ConversationController.getOrCreateAndWait(
|
||||
number || uuid,
|
||||
'private'
|
||||
);
|
||||
conversation.updateE164(number);
|
||||
conversation.updateUuid(uuid);
|
||||
|
||||
window.log.info('dispatching registration event');
|
||||
|
||||
|
|
|
@ -36,6 +36,22 @@ ProtoParser.prototype = {
|
|||
proto.profileKey = proto.profileKey.toArrayBuffer();
|
||||
}
|
||||
|
||||
if (proto.uuid) {
|
||||
window.normalizeUuids(
|
||||
proto,
|
||||
['uuid'],
|
||||
'ProtoParser::next (proto.uuid)'
|
||||
);
|
||||
}
|
||||
|
||||
if (proto.members) {
|
||||
window.normalizeUuids(
|
||||
proto,
|
||||
proto.members.map((_member, i) => `members.${i}.uuid`),
|
||||
'ProtoParser::next (proto.members)'
|
||||
);
|
||||
}
|
||||
|
||||
return proto;
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
|
|
|
@ -36521,7 +36521,7 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
|
|||
})();
|
||||
|
||||
(function() {
|
||||
var VERSION = 0;
|
||||
var VERSION = shortToArrayBuffer(0);
|
||||
|
||||
function iterateHash(data, key, count) {
|
||||
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
|
||||
|
@ -36551,10 +36551,21 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
|
|||
return s;
|
||||
}
|
||||
|
||||
function decodeUuid(uuid) {
|
||||
let i = 0;
|
||||
let buf = new Uint8Array(16);
|
||||
|
||||
uuid.replace(/[0-9A-F]{2}/ig, oct => {
|
||||
buf[i++] = parseInt(oct, 16);
|
||||
});
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
function getDisplayStringFor(identifier, key, iterations) {
|
||||
var bytes = dcodeIO.ByteBuffer.concat([
|
||||
shortToArrayBuffer(VERSION), key, identifier
|
||||
]).toArrayBuffer();
|
||||
var isUuid = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(identifier);
|
||||
var encodedIdentifier = isUuid ? decodeUuid(identifier) : identifier;
|
||||
var bytes = dcodeIO.ByteBuffer.concat([VERSION, key, encodedIdentifier]).toArrayBuffer();
|
||||
return iterateHash(bytes, key, iterations).then(function(output) {
|
||||
output = new Uint8Array(output);
|
||||
return getEncodedChunk(output, 0) +
|
||||
|
|
|
@ -14,13 +14,20 @@
|
|||
|
||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||
|
||||
function MessageReceiver(username, password, signalingKey, options = {}) {
|
||||
function MessageReceiver(
|
||||
oldUsername,
|
||||
username,
|
||||
password,
|
||||
signalingKey,
|
||||
options = {}
|
||||
) {
|
||||
this.count = 0;
|
||||
|
||||
this.signalingKey = signalingKey;
|
||||
this.username = username;
|
||||
this.username = oldUsername;
|
||||
this.uuid = username;
|
||||
this.password = password;
|
||||
this.server = WebAPI.connect({ username, password });
|
||||
this.server = WebAPI.connect({ username: username || oldUsername, password });
|
||||
|
||||
if (!options.serverTrustRoot) {
|
||||
throw new Error('Server trust root is required!');
|
||||
|
@ -29,9 +36,12 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
|
|||
options.serverTrustRoot
|
||||
);
|
||||
|
||||
const address = libsignal.SignalProtocolAddress.fromString(username);
|
||||
this.number = address.getName();
|
||||
this.deviceId = address.getDeviceId();
|
||||
this.number_id = oldUsername
|
||||
? textsecure.utils.unencodeNumber(oldUsername)[0]
|
||||
: null;
|
||||
this.uuid_id = username ? textsecure.utils.unencodeNumber(username)[0] : null;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
this.deviceId = textsecure.utils.unencodeNumber(username || oldUsername)[1];
|
||||
|
||||
this.incomingQueue = new window.PQueue({ concurrency: 1 });
|
||||
this.pendingQueue = new window.PQueue({ concurrency: 1 });
|
||||
|
@ -176,7 +186,7 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
// possible 403 or network issue. Make an request to confirm
|
||||
return this.server
|
||||
.getDevices(this.number)
|
||||
.getDevices(this.number_id || this.uuid_id)
|
||||
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
||||
.catch(e => {
|
||||
const event = new Event('error');
|
||||
|
@ -213,6 +223,11 @@ MessageReceiver.prototype.extend({
|
|||
|
||||
try {
|
||||
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
||||
window.normalizeUuids(
|
||||
envelope,
|
||||
['sourceUuid'],
|
||||
'message_receiver::handleRequest::job'
|
||||
);
|
||||
// After this point, decoding errors are not the server's
|
||||
// fault, and we should handle them gracefully and tell the
|
||||
// user they received an invalid message
|
||||
|
@ -222,6 +237,11 @@ MessageReceiver.prototype.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.isUuidBlocked(envelope.sourceUuid)) {
|
||||
request.respond(200, 'OK');
|
||||
return;
|
||||
}
|
||||
|
||||
envelope.id = envelope.serverGuid || window.getGuid();
|
||||
envelope.serverTimestamp = envelope.serverTimestamp
|
||||
? envelope.serverTimestamp.toNumber()
|
||||
|
@ -333,6 +353,7 @@ MessageReceiver.prototype.extend({
|
|||
const envelope = textsecure.protobuf.Envelope.decode(envelopePlaintext);
|
||||
envelope.id = envelope.serverGuid || item.id;
|
||||
envelope.source = envelope.source || item.source;
|
||||
envelope.sourceUuid = envelope.sourceUuid || item.sourceUuid;
|
||||
envelope.sourceDevice = envelope.sourceDevice || item.sourceDevice;
|
||||
envelope.serverTimestamp =
|
||||
envelope.serverTimestamp || item.serverTimestamp;
|
||||
|
@ -378,8 +399,8 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
},
|
||||
getEnvelopeId(envelope) {
|
||||
if (envelope.source) {
|
||||
return `${envelope.source}.${
|
||||
if (envelope.sourceUuid || envelope.source) {
|
||||
return `${envelope.sourceUuid || envelope.source}.${
|
||||
envelope.sourceDevice
|
||||
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
|
||||
}
|
||||
|
@ -485,6 +506,7 @@ MessageReceiver.prototype.extend({
|
|||
const { id } = envelope;
|
||||
const data = {
|
||||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
serverTimestamp: envelope.serverTimestamp,
|
||||
decrypted: MessageReceiver.arrayBufferToStringBase64(plaintext),
|
||||
|
@ -586,6 +608,7 @@ MessageReceiver.prototype.extend({
|
|||
ev.deliveryReceipt = {
|
||||
timestamp: envelope.timestamp.toNumber(),
|
||||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
};
|
||||
this.dispatchAndWait(ev).then(resolve, reject);
|
||||
|
@ -613,16 +636,21 @@ MessageReceiver.prototype.extend({
|
|||
|
||||
let promise;
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
envelope.source,
|
||||
// Using source as opposed to sourceUuid allows us to get the existing
|
||||
// session if we haven't yet harvested the incoming uuid
|
||||
envelope.source || envelope.sourceUuid,
|
||||
envelope.sourceDevice
|
||||
);
|
||||
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const number = address.toString().split('.')[0];
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const options = {};
|
||||
|
||||
// No limit on message keys if we're communicating with our other devices
|
||||
if (ourNumber === number) {
|
||||
if (
|
||||
(envelope.source && ourNumber && ourNumber === envelope.source) ||
|
||||
(envelope.sourceUuid && ourUuid && ourUuid === envelope.sourceUuid)
|
||||
) {
|
||||
options.messageKeysLimit = false;
|
||||
}
|
||||
|
||||
|
@ -637,6 +665,7 @@ MessageReceiver.prototype.extend({
|
|||
|
||||
const me = {
|
||||
number: ourNumber,
|
||||
uuid: ourUuid,
|
||||
deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10),
|
||||
};
|
||||
|
||||
|
@ -666,7 +695,7 @@ MessageReceiver.prototype.extend({
|
|||
)
|
||||
.then(
|
||||
result => {
|
||||
const { isMe, sender, content } = result;
|
||||
const { isMe, sender, senderUuid, content } = result;
|
||||
|
||||
// We need to drop incoming messages from ourself since server can't
|
||||
// do it for us
|
||||
|
@ -674,7 +703,10 @@ MessageReceiver.prototype.extend({
|
|||
return { isMe: true };
|
||||
}
|
||||
|
||||
if (this.isBlocked(sender.getName())) {
|
||||
if (
|
||||
(sender && this.isBlocked(sender.getName())) ||
|
||||
(senderUuid && this.isUuidBlocked(senderUuid.getName()))
|
||||
) {
|
||||
window.log.info(
|
||||
'Dropping blocked message after sealed sender decryption'
|
||||
);
|
||||
|
@ -685,25 +717,41 @@ MessageReceiver.prototype.extend({
|
|||
// to make the rest of the app work properly.
|
||||
|
||||
const originalSource = envelope.source;
|
||||
const originalSourceUuid = envelope.sourceUuid;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.source = sender.getName();
|
||||
envelope.source = sender && sender.getName();
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.sourceDevice = sender.getDeviceId();
|
||||
envelope.sourceUuid = senderUuid && senderUuid.getName();
|
||||
window.normalizeUuids(
|
||||
envelope,
|
||||
['sourceUuid'],
|
||||
'message_receiver::decrypt::UNIDENTIFIED_SENDER'
|
||||
);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.unidentifiedDeliveryReceived = !originalSource;
|
||||
envelope.sourceDevice =
|
||||
(sender && sender.getDeviceId()) ||
|
||||
(senderUuid && senderUuid.getDeviceId());
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.unidentifiedDeliveryReceived = !(
|
||||
originalSource || originalSourceUuid
|
||||
);
|
||||
|
||||
// Return just the content because that matches the signature of the other
|
||||
// decrypt methods used above.
|
||||
return this.unpad(content);
|
||||
},
|
||||
error => {
|
||||
const { sender } = error || {};
|
||||
const { sender, senderUuid } = error || {};
|
||||
|
||||
if (sender) {
|
||||
if (sender || senderUuid) {
|
||||
const originalSource = envelope.source;
|
||||
const originalSourceUuid = envelope.sourceUuid;
|
||||
|
||||
if (this.isBlocked(sender.getName())) {
|
||||
if (
|
||||
(sender && this.isBlocked(sender.getName())) ||
|
||||
(senderUuid && this.isUuidBlocked(senderUuid.getName()))
|
||||
) {
|
||||
window.log.info(
|
||||
'Dropping blocked message with error after sealed sender decryption'
|
||||
);
|
||||
|
@ -711,11 +759,23 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.source = sender.getName();
|
||||
envelope.source = sender && sender.getName();
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.sourceDevice = sender.getDeviceId();
|
||||
envelope.sourceUuid =
|
||||
senderUuid && senderUuid.getName().toLowerCase();
|
||||
window.normalizeUuids(
|
||||
envelope,
|
||||
['sourceUuid'],
|
||||
'message_receiver::decrypt::UNIDENTIFIED_SENDER::error'
|
||||
);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.unidentifiedDeliveryReceived = !originalSource;
|
||||
envelope.sourceDevice =
|
||||
(sender && sender.getDeviceId()) ||
|
||||
(senderUuid && senderUuid.getDeviceId());
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
envelope.unidentifiedDeliveryReceived = !(
|
||||
originalSource || originalSourceUuid
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
@ -803,7 +863,12 @@ MessageReceiver.prototype.extend({
|
|||
this.processDecrypted(envelope, msg).then(message => {
|
||||
const groupId = message.group && message.group.id;
|
||||
const isBlocked = this.isGroupBlocked(groupId);
|
||||
const isMe = envelope.source === textsecure.storage.user.getNumber();
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
const isLeavingGroup = Boolean(
|
||||
message.group &&
|
||||
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
||||
|
@ -840,13 +905,18 @@ MessageReceiver.prototype.extend({
|
|||
let p = Promise.resolve();
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
||||
p = this.handleEndSession(envelope.source);
|
||||
p = this.handleEndSession(envelope.source || envelope.sourceUuid);
|
||||
}
|
||||
return p.then(() =>
|
||||
this.processDecrypted(envelope, msg).then(message => {
|
||||
const groupId = message.group && message.group.id;
|
||||
const isBlocked = this.isGroupBlocked(groupId);
|
||||
const isMe = envelope.source === textsecure.storage.user.getNumber();
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
const isLeavingGroup = Boolean(
|
||||
message.group &&
|
||||
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
||||
|
@ -865,6 +935,7 @@ MessageReceiver.prototype.extend({
|
|||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
ev.data = {
|
||||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
timestamp: envelope.timestamp.toNumber(),
|
||||
receivedAt: envelope.receivedAt,
|
||||
|
@ -930,6 +1001,7 @@ MessageReceiver.prototype.extend({
|
|||
ev.deliveryReceipt = {
|
||||
timestamp: receiptMessage.timestamp[i].toNumber(),
|
||||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
};
|
||||
results.push(this.dispatchAndWait(ev));
|
||||
|
@ -943,7 +1015,7 @@ MessageReceiver.prototype.extend({
|
|||
ev.timestamp = envelope.timestamp.toNumber();
|
||||
ev.read = {
|
||||
timestamp: receiptMessage.timestamp[i].toNumber(),
|
||||
reader: envelope.source,
|
||||
reader: envelope.source || envelope.sourceUuid,
|
||||
};
|
||||
results.push(this.dispatchAndWait(ev));
|
||||
}
|
||||
|
@ -968,6 +1040,7 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
|
||||
ev.sender = envelope.source;
|
||||
ev.senderUuid = envelope.sourceUuid;
|
||||
ev.senderDevice = envelope.sourceDevice;
|
||||
ev.typing = {
|
||||
typingMessage,
|
||||
|
@ -992,7 +1065,24 @@ MessageReceiver.prototype.extend({
|
|||
this.removeFromCache(envelope);
|
||||
},
|
||||
handleSyncMessage(envelope, syncMessage) {
|
||||
if (envelope.source !== this.number) {
|
||||
const unidentified = syncMessage.sent
|
||||
? syncMessage.sent.unidentifiedStatus || []
|
||||
: [];
|
||||
window.normalizeUuids(
|
||||
syncMessage,
|
||||
[
|
||||
'sent.destinationUuid',
|
||||
...unidentified.map(
|
||||
(_el, i) => `sent.unidentifiedStatus.${i}.destinationUuid`
|
||||
),
|
||||
],
|
||||
'message_receiver::handleSyncMessage'
|
||||
);
|
||||
const fromSelfSource =
|
||||
envelope.source && envelope.source === this.number_id;
|
||||
const fromSelfSourceUuid =
|
||||
envelope.sourceUuid && envelope.sourceUuid === this.uuid_id;
|
||||
if (!fromSelfSource && !fromSelfSourceUuid) {
|
||||
throw new Error('Received sync message from another number');
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
|
@ -1057,8 +1147,15 @@ MessageReceiver.prototype.extend({
|
|||
const ev = new Event('viewSync');
|
||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
ev.source = sync.sender;
|
||||
ev.sourceUuid = sync.senderUuid;
|
||||
ev.timestamp = sync.timestamp ? sync.timestamp.toNumber() : null;
|
||||
|
||||
window.normalizeUuids(
|
||||
ev,
|
||||
['sourceUuid'],
|
||||
'message_receiver::handleViewOnceOpen'
|
||||
);
|
||||
|
||||
return this.dispatchAndWait(ev);
|
||||
},
|
||||
handleStickerPackOperation(envelope, operations) {
|
||||
|
@ -1080,8 +1177,14 @@ MessageReceiver.prototype.extend({
|
|||
ev.verified = {
|
||||
state: verified.state,
|
||||
destination: verified.destination,
|
||||
destinationUuid: verified.destinationUuid,
|
||||
identityKey: verified.identityKey.toArrayBuffer(),
|
||||
};
|
||||
window.normalizeUuids(
|
||||
ev,
|
||||
['verified.destinationUuid'],
|
||||
'message_receiver::handleVerified'
|
||||
);
|
||||
return this.dispatchAndWait(ev);
|
||||
},
|
||||
handleRead(envelope, read) {
|
||||
|
@ -1093,7 +1196,13 @@ MessageReceiver.prototype.extend({
|
|||
ev.read = {
|
||||
timestamp: read[i].timestamp.toNumber(),
|
||||
sender: read[i].sender,
|
||||
senderUuid: read[i].senderUuid,
|
||||
};
|
||||
window.normalizeUuids(
|
||||
ev,
|
||||
['read.senderUuid'],
|
||||
'message_receiver::handleRead'
|
||||
);
|
||||
results.push(this.dispatchAndWait(ev));
|
||||
}
|
||||
return Promise.all(results);
|
||||
|
@ -1158,6 +1267,15 @@ MessageReceiver.prototype.extend({
|
|||
handleBlocked(envelope, blocked) {
|
||||
window.log.info('Setting these numbers as blocked:', blocked.numbers);
|
||||
textsecure.storage.put('blocked', blocked.numbers);
|
||||
if (blocked.uuids) {
|
||||
window.normalizeUuids(
|
||||
blocked,
|
||||
blocked.uuids.map((_uuid, i) => `uuids.${i}`),
|
||||
'message_receiver::handleBlocked'
|
||||
);
|
||||
window.log.info('Setting these uuids as blocked:', blocked.uuids);
|
||||
textsecure.storage.put('blocked-uuids', blocked.uuids);
|
||||
}
|
||||
|
||||
const groupIds = _.map(blocked.groupIds, groupId => groupId.toBinary());
|
||||
window.log.info(
|
||||
|
@ -1169,10 +1287,13 @@ MessageReceiver.prototype.extend({
|
|||
return this.removeFromCache(envelope);
|
||||
},
|
||||
isBlocked(number) {
|
||||
return textsecure.storage.get('blocked', []).indexOf(number) >= 0;
|
||||
return textsecure.storage.get('blocked', []).includes(number);
|
||||
},
|
||||
isUuidBlocked(uuid) {
|
||||
return textsecure.storage.get('blocked-uuids', []).includes(uuid);
|
||||
},
|
||||
isGroupBlocked(groupId) {
|
||||
return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0;
|
||||
return textsecure.storage.get('blocked-groups', []).includes(groupId);
|
||||
},
|
||||
cleanAttachment(attachment) {
|
||||
return {
|
||||
|
@ -1213,13 +1334,18 @@ MessageReceiver.prototype.extend({
|
|||
const cleaned = this.cleanAttachment(attachment);
|
||||
return this.downloadAttachment(cleaned);
|
||||
},
|
||||
async handleEndSession(number) {
|
||||
async handleEndSession(identifier) {
|
||||
window.log.info('got end session');
|
||||
const deviceIds = await textsecure.storage.protocol.getDeviceIds(number);
|
||||
const deviceIds = await textsecure.storage.protocol.getDeviceIds(
|
||||
identifier
|
||||
);
|
||||
|
||||
return Promise.all(
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
identifier,
|
||||
deviceId
|
||||
);
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
|
@ -1274,8 +1400,6 @@ MessageReceiver.prototype.extend({
|
|||
throw new Error('Unknown flags in message');
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (decrypted.group !== null) {
|
||||
decrypted.group.id = decrypted.group.id.toBinary();
|
||||
|
||||
|
@ -1290,6 +1414,7 @@ MessageReceiver.prototype.extend({
|
|||
break;
|
||||
case textsecure.protobuf.GroupContext.Type.DELIVER:
|
||||
decrypted.group.name = null;
|
||||
decrypted.group.membersE164 = [];
|
||||
decrypted.group.members = [];
|
||||
decrypted.group.avatar = null;
|
||||
break;
|
||||
|
@ -1383,7 +1508,19 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => decrypted);
|
||||
const groupMembers = decrypted.group ? decrypted.group.members || [] : [];
|
||||
|
||||
window.normalizeUuids(
|
||||
decrypted,
|
||||
[
|
||||
'quote.authorUuid',
|
||||
'reaction.targetAuthorUuid',
|
||||
...groupMembers.map((_member, i) => `group.members.${i}.uuid`),
|
||||
],
|
||||
'message_receiver::processDecrypted'
|
||||
);
|
||||
|
||||
return Promise.resolve(decrypted);
|
||||
/* eslint-enable no-bitwise, no-param-reassign */
|
||||
},
|
||||
});
|
||||
|
@ -1392,12 +1529,14 @@ window.textsecure = window.textsecure || {};
|
|||
|
||||
textsecure.MessageReceiver = function MessageReceiverWrapper(
|
||||
username,
|
||||
uuid,
|
||||
password,
|
||||
signalingKey,
|
||||
options
|
||||
) {
|
||||
const messageReceiver = new MessageReceiver(
|
||||
username,
|
||||
uuid,
|
||||
password,
|
||||
signalingKey,
|
||||
options
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
function OutgoingMessage(
|
||||
server,
|
||||
timestamp,
|
||||
numbers,
|
||||
identifiers,
|
||||
message,
|
||||
silent,
|
||||
callback,
|
||||
|
@ -19,41 +19,43 @@ function OutgoingMessage(
|
|||
}
|
||||
this.server = server;
|
||||
this.timestamp = timestamp;
|
||||
this.numbers = numbers;
|
||||
this.identifiers = identifiers;
|
||||
this.message = message; // ContentMessage proto
|
||||
this.callback = callback;
|
||||
this.silent = silent;
|
||||
|
||||
this.numbersCompleted = 0;
|
||||
this.identifiersCompleted = 0;
|
||||
this.errors = [];
|
||||
this.successfulNumbers = [];
|
||||
this.failoverNumbers = [];
|
||||
this.successfulIdentifiers = [];
|
||||
this.failoverIdentifiers = [];
|
||||
this.unidentifiedDeliveries = [];
|
||||
|
||||
const { numberInfo, senderCertificate, online } = options || {};
|
||||
this.numberInfo = numberInfo;
|
||||
const { sendMetadata, senderCertificate, senderCertificateWithUuid, online } =
|
||||
options || {};
|
||||
this.sendMetadata = sendMetadata;
|
||||
this.senderCertificate = senderCertificate;
|
||||
this.senderCertificateWithUuid = senderCertificateWithUuid;
|
||||
this.online = online;
|
||||
}
|
||||
|
||||
OutgoingMessage.prototype = {
|
||||
constructor: OutgoingMessage,
|
||||
numberCompleted() {
|
||||
this.numbersCompleted += 1;
|
||||
if (this.numbersCompleted >= this.numbers.length) {
|
||||
this.identifiersCompleted += 1;
|
||||
if (this.identifiersCompleted >= this.identifiers.length) {
|
||||
this.callback({
|
||||
successfulNumbers: this.successfulNumbers,
|
||||
failoverNumbers: this.failoverNumbers,
|
||||
successfulIdentifiers: this.successfulIdentifiers,
|
||||
failoverIdentifiers: this.failoverIdentifiers,
|
||||
errors: this.errors,
|
||||
unidentifiedDeliveries: this.unidentifiedDeliveries,
|
||||
});
|
||||
}
|
||||
},
|
||||
registerError(number, reason, error) {
|
||||
registerError(identifier, reason, error) {
|
||||
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error = new textsecure.OutgoingMessageError(
|
||||
number,
|
||||
identifier,
|
||||
this.message.toArrayBuffer(),
|
||||
this.timestamp,
|
||||
error
|
||||
|
@ -61,27 +63,27 @@ OutgoingMessage.prototype = {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.number = number;
|
||||
error.number = identifier;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.reason = reason;
|
||||
this.errors[this.errors.length] = error;
|
||||
this.numberCompleted();
|
||||
},
|
||||
reloadDevicesAndSend(number, recurse) {
|
||||
reloadDevicesAndSend(identifier, recurse) {
|
||||
return () =>
|
||||
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||
textsecure.storage.protocol.getDeviceIds(identifier).then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return this.registerError(
|
||||
number,
|
||||
identifier,
|
||||
'Got empty device list when loading device keys',
|
||||
null
|
||||
);
|
||||
}
|
||||
return this.doSendMessage(number, deviceIds, recurse);
|
||||
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||
});
|
||||
},
|
||||
|
||||
getKeysForNumber(number, updateDevices) {
|
||||
getKeysForIdentifier(identifier, updateDevices) {
|
||||
const handleResult = response =>
|
||||
Promise.all(
|
||||
response.devices.map(device => {
|
||||
|
@ -92,7 +94,7 @@ OutgoingMessage.prototype = {
|
|||
updateDevices.indexOf(device.deviceId) > -1
|
||||
) {
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
number,
|
||||
identifier,
|
||||
device.deviceId
|
||||
);
|
||||
const builder = new libsignal.SessionBuilder(
|
||||
|
@ -119,27 +121,30 @@ OutgoingMessage.prototype = {
|
|||
})
|
||||
);
|
||||
|
||||
const { numberInfo } = this;
|
||||
const info = numberInfo && numberInfo[number] ? numberInfo[number] : {};
|
||||
const { sendMetadata } = this;
|
||||
const info =
|
||||
sendMetadata && sendMetadata[identifier] ? sendMetadata[identifier] : {};
|
||||
const { accessKey } = info || {};
|
||||
|
||||
if (updateDevices === undefined) {
|
||||
if (accessKey) {
|
||||
return this.server
|
||||
.getKeysForNumberUnauth(number, '*', { accessKey })
|
||||
.getKeysForIdentifierUnauth(identifier, '*', { accessKey })
|
||||
.catch(error => {
|
||||
if (error.code === 401 || error.code === 403) {
|
||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
||||
this.failoverNumbers.push(number);
|
||||
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
}
|
||||
return this.server.getKeysForNumber(number, '*');
|
||||
return this.server.getKeysForIdentifier(identifier, '*');
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(handleResult);
|
||||
}
|
||||
|
||||
return this.server.getKeysForNumber(number, '*').then(handleResult);
|
||||
return this.server
|
||||
.getKeysForIdentifier(identifier, '*')
|
||||
.then(handleResult);
|
||||
}
|
||||
|
||||
let promise = Promise.resolve();
|
||||
|
@ -149,31 +154,31 @@ OutgoingMessage.prototype = {
|
|||
|
||||
if (accessKey) {
|
||||
innerPromise = this.server
|
||||
.getKeysForNumberUnauth(number, deviceId, { accessKey })
|
||||
.getKeysForIdentifierUnauth(identifier, deviceId, { accessKey })
|
||||
.then(handleResult)
|
||||
.catch(error => {
|
||||
if (error.code === 401 || error.code === 403) {
|
||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
||||
this.failoverNumbers.push(number);
|
||||
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
}
|
||||
return this.server
|
||||
.getKeysForNumber(number, deviceId)
|
||||
.getKeysForIdentifier(identifier, deviceId)
|
||||
.then(handleResult);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
} else {
|
||||
innerPromise = this.server
|
||||
.getKeysForNumber(number, deviceId)
|
||||
.getKeysForIdentifier(identifier, deviceId)
|
||||
.then(handleResult);
|
||||
}
|
||||
|
||||
return innerPromise.catch(e => {
|
||||
if (e.name === 'HTTPError' && e.code === 404) {
|
||||
if (deviceId !== 1) {
|
||||
return this.removeDeviceIdsForNumber(number, [deviceId]);
|
||||
return this.removeDeviceIdsForIdentifier(identifier, [deviceId]);
|
||||
}
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
throw new textsecure.UnregisteredUserError(identifier, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
@ -184,12 +189,12 @@ OutgoingMessage.prototype = {
|
|||
return promise;
|
||||
},
|
||||
|
||||
transmitMessage(number, jsonData, timestamp, { accessKey } = {}) {
|
||||
transmitMessage(identifier, jsonData, timestamp, { accessKey } = {}) {
|
||||
let promise;
|
||||
|
||||
if (accessKey) {
|
||||
promise = this.server.sendMessagesUnauth(
|
||||
number,
|
||||
identifier,
|
||||
jsonData,
|
||||
timestamp,
|
||||
this.silent,
|
||||
|
@ -198,7 +203,7 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
} else {
|
||||
promise = this.server.sendMessages(
|
||||
number,
|
||||
identifier,
|
||||
jsonData,
|
||||
timestamp,
|
||||
this.silent,
|
||||
|
@ -212,10 +217,10 @@ OutgoingMessage.prototype = {
|
|||
// 404 should throw UnregisteredUserError
|
||||
// all other network errors can be retried later.
|
||||
if (e.code === 404) {
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
throw new textsecure.UnregisteredUserError(identifier, e);
|
||||
}
|
||||
throw new textsecure.SendMessageNetworkError(
|
||||
number,
|
||||
identifier,
|
||||
jsonData,
|
||||
e,
|
||||
timestamp
|
||||
|
@ -248,13 +253,17 @@ OutgoingMessage.prototype = {
|
|||
return this.plaintext;
|
||||
},
|
||||
|
||||
doSendMessage(number, deviceIds, recurse) {
|
||||
doSendMessage(identifier, deviceIds, recurse) {
|
||||
const ciphers = {};
|
||||
const plaintext = this.getPlaintext();
|
||||
|
||||
const { numberInfo, senderCertificate } = this;
|
||||
const info = numberInfo && numberInfo[number] ? numberInfo[number] : {};
|
||||
const { accessKey } = info || {};
|
||||
const { sendMetadata } = this;
|
||||
const info =
|
||||
sendMetadata && sendMetadata[identifier] ? sendMetadata[identifier] : {};
|
||||
const { accessKey, useUuidSenderCert } = info || {};
|
||||
const senderCertificate = useUuidSenderCert
|
||||
? this.senderCertificateWithUuid
|
||||
: this.senderCertificate;
|
||||
|
||||
if (accessKey && !senderCertificate) {
|
||||
window.log.warn(
|
||||
|
@ -266,8 +275,9 @@ OutgoingMessage.prototype = {
|
|||
|
||||
// We don't send to ourselves if unless sealedSender is enabled
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const ourDeviceId = textsecure.storage.user.getDeviceId();
|
||||
if (number === ourNumber && !sealedSender) {
|
||||
if ((identifier === ourNumber || identifier === ourUuid) && !sealedSender) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
deviceIds = _.reject(
|
||||
deviceIds,
|
||||
|
@ -279,12 +289,15 @@ OutgoingMessage.prototype = {
|
|||
|
||||
return Promise.all(
|
||||
deviceIds.map(async deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
identifier,
|
||||
deviceId
|
||||
);
|
||||
|
||||
const options = {};
|
||||
|
||||
// No limit on message keys if we're communicating with our other devices
|
||||
if (ourNumber === number) {
|
||||
if (ourNumber === identifier || ourUuid === identifier) {
|
||||
options.messageKeysLimit = false;
|
||||
}
|
||||
|
||||
|
@ -299,6 +312,7 @@ OutgoingMessage.prototype = {
|
|||
senderCertificate,
|
||||
plaintext
|
||||
);
|
||||
|
||||
return {
|
||||
type: textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER,
|
||||
destinationDeviceId: address.getDeviceId(),
|
||||
|
@ -327,18 +341,18 @@ OutgoingMessage.prototype = {
|
|||
)
|
||||
.then(jsonData => {
|
||||
if (sealedSender) {
|
||||
return this.transmitMessage(number, jsonData, this.timestamp, {
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp, {
|
||||
accessKey,
|
||||
}).then(
|
||||
() => {
|
||||
this.unidentifiedDeliveries.push(number);
|
||||
this.successfulNumbers.push(number);
|
||||
this.unidentifiedDeliveries.push(identifier);
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.numberCompleted();
|
||||
},
|
||||
error => {
|
||||
if (error.code === 401 || error.code === 403) {
|
||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
||||
this.failoverNumbers.push(number);
|
||||
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
}
|
||||
if (info) {
|
||||
info.accessKey = null;
|
||||
|
@ -346,7 +360,7 @@ OutgoingMessage.prototype = {
|
|||
|
||||
// Set final parameter to true to ensure we don't hit this codepath a
|
||||
// second time.
|
||||
return this.doSendMessage(number, deviceIds, recurse, true);
|
||||
return this.doSendMessage(identifier, deviceIds, recurse, true);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -354,9 +368,9 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
}
|
||||
|
||||
return this.transmitMessage(number, jsonData, this.timestamp).then(
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp).then(
|
||||
() => {
|
||||
this.successfulNumbers.push(number);
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.numberCompleted();
|
||||
}
|
||||
);
|
||||
|
@ -369,22 +383,22 @@ OutgoingMessage.prototype = {
|
|||
) {
|
||||
if (!recurse)
|
||||
return this.registerError(
|
||||
number,
|
||||
identifier,
|
||||
'Hit retry limit attempting to reload device list',
|
||||
error
|
||||
);
|
||||
|
||||
let p;
|
||||
if (error.code === 409) {
|
||||
p = this.removeDeviceIdsForNumber(
|
||||
number,
|
||||
p = this.removeDeviceIdsForIdentifier(
|
||||
identifier,
|
||||
error.response.extraDevices
|
||||
);
|
||||
} else {
|
||||
p = Promise.all(
|
||||
error.response.staleDevices.map(deviceId =>
|
||||
ciphers[deviceId].closeOpenSessionForDevice(
|
||||
new libsignal.SignalProtocolAddress(number, deviceId)
|
||||
new libsignal.SignalProtocolAddress(identifier, deviceId)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -395,10 +409,10 @@ OutgoingMessage.prototype = {
|
|||
error.code === 410
|
||||
? error.response.staleDevices
|
||||
: error.response.missingDevices;
|
||||
return this.getKeysForNumber(number, resetDevices).then(
|
||||
return this.getKeysForIdentifier(identifier, resetDevices).then(
|
||||
// We continue to retry as long as the error code was 409; the assumption is
|
||||
// that we'll request new device info and the next request will succeed.
|
||||
this.reloadDevicesAndSend(number, error.code === 409)
|
||||
this.reloadDevicesAndSend(identifier, error.code === 409)
|
||||
);
|
||||
});
|
||||
} else if (error.message === 'Identity key changed') {
|
||||
|
@ -408,13 +422,12 @@ OutgoingMessage.prototype = {
|
|||
error.originalMessage = this.message.toArrayBuffer();
|
||||
window.log.error(
|
||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||
number,
|
||||
identifier,
|
||||
deviceIds
|
||||
);
|
||||
|
||||
const address = new libsignal.SignalProtocolAddress(number, 1);
|
||||
const identifier = address.toString();
|
||||
window.log.info('closing all sessions for', number);
|
||||
window.log.info('closing all sessions for', identifier);
|
||||
const address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
|
@ -425,7 +438,9 @@ OutgoingMessage.prototype = {
|
|||
// Primary device
|
||||
sessionCipher.closeOpenSessionForDevice(),
|
||||
// The rest of their devices
|
||||
textsecure.storage.protocol.archiveSiblingSessions(identifier),
|
||||
textsecure.storage.protocol.archiveSiblingSessions(
|
||||
address.toString()
|
||||
),
|
||||
]).then(
|
||||
() => {
|
||||
throw error;
|
||||
|
@ -439,65 +454,76 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
}
|
||||
|
||||
this.registerError(number, 'Failed to create or send message', error);
|
||||
this.registerError(
|
||||
identifier,
|
||||
'Failed to create or send message',
|
||||
error
|
||||
);
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
getStaleDeviceIdsForNumber(number) {
|
||||
return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return [1];
|
||||
}
|
||||
const updateDevices = [];
|
||||
return Promise.all(
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
return sessionCipher.hasOpenSession().then(hasSession => {
|
||||
if (!hasSession) {
|
||||
updateDevices.push(deviceId);
|
||||
}
|
||||
});
|
||||
})
|
||||
).then(() => updateDevices);
|
||||
});
|
||||
getStaleDeviceIdsForIdentifier(identifier) {
|
||||
return textsecure.storage.protocol
|
||||
.getDeviceIds(identifier)
|
||||
.then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return [1];
|
||||
}
|
||||
const updateDevices = [];
|
||||
return Promise.all(
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
identifier,
|
||||
deviceId
|
||||
);
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
return sessionCipher.hasOpenSession().then(hasSession => {
|
||||
if (!hasSession) {
|
||||
updateDevices.push(deviceId);
|
||||
}
|
||||
});
|
||||
})
|
||||
).then(() => updateDevices);
|
||||
});
|
||||
},
|
||||
|
||||
removeDeviceIdsForNumber(number, deviceIdsToRemove) {
|
||||
removeDeviceIdsForIdentifier(identifier, deviceIdsToRemove) {
|
||||
let promise = Promise.resolve();
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const j in deviceIdsToRemove) {
|
||||
promise = promise.then(() => {
|
||||
const encodedNumber = `${number}.${deviceIdsToRemove[j]}`;
|
||||
return textsecure.storage.protocol.removeSession(encodedNumber);
|
||||
const encodedAddress = `${identifier}.${deviceIdsToRemove[j]}`;
|
||||
return textsecure.storage.protocol.removeSession(encodedAddress);
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
|
||||
async sendToNumber(number) {
|
||||
async sendToIdentifier(identifier) {
|
||||
try {
|
||||
const updateDevices = await this.getStaleDeviceIdsForNumber(number);
|
||||
await this.getKeysForNumber(number, updateDevices);
|
||||
await this.reloadDevicesAndSend(number, true)();
|
||||
const updateDevices = await this.getStaleDeviceIdsForIdentifier(
|
||||
identifier
|
||||
);
|
||||
await this.getKeysForIdentifier(identifier, updateDevices);
|
||||
await this.reloadDevicesAndSend(identifier, true)();
|
||||
} catch (error) {
|
||||
if (error.message === 'Identity key changed') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
const newError = new textsecure.OutgoingIdentityKeyError(
|
||||
number,
|
||||
identifier,
|
||||
error.originalMessage,
|
||||
error.timestamp,
|
||||
error.identityKey
|
||||
);
|
||||
this.registerError(number, 'Identity key changed', newError);
|
||||
this.registerError(identifier, 'Identity key changed', newError);
|
||||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
`Failed to retrieve new device keys for number ${number}`,
|
||||
identifier,
|
||||
`Failed to retrieve new device keys for number ${identifier}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, dcodeIO */
|
||||
// eslint-disable-next-line max-len
|
||||
/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, dcodeIO, ConversationController */
|
||||
|
||||
/* eslint-disable more/no-then, no-bitwise */
|
||||
|
||||
|
@ -246,18 +247,22 @@ MessageSender.prototype = {
|
|||
return proto;
|
||||
},
|
||||
|
||||
queueJobForNumber(number, runJob) {
|
||||
this.pendingMessages[number] =
|
||||
this.pendingMessages[number] || new window.PQueue({ concurrency: 1 });
|
||||
async queueJobForIdentifier(identifier, runJob) {
|
||||
const { id } = await ConversationController.getOrCreateAndWait(
|
||||
identifier,
|
||||
'private'
|
||||
);
|
||||
this.pendingMessages[id] =
|
||||
this.pendingMessages[id] || new window.PQueue({ concurrency: 1 });
|
||||
|
||||
const queue = this.pendingMessages[number];
|
||||
const queue = this.pendingMessages[id];
|
||||
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(
|
||||
runJob,
|
||||
`queueJobForNumber ${number}`
|
||||
`queueJobForIdentifier ${identifier} ${id}`
|
||||
);
|
||||
|
||||
queue.add(taskWithTimeout);
|
||||
return queue.add(taskWithTimeout);
|
||||
},
|
||||
|
||||
uploadAttachments(message) {
|
||||
|
@ -361,7 +366,7 @@ MessageSender.prototype = {
|
|||
new Promise((resolve, reject) => {
|
||||
this.sendMessageProto(
|
||||
message.timestamp,
|
||||
message.recipients,
|
||||
message.recipients || [],
|
||||
message.toProto(),
|
||||
res => {
|
||||
res.dataMessage = message.toArrayBuffer();
|
||||
|
@ -379,8 +384,8 @@ MessageSender.prototype = {
|
|||
},
|
||||
sendMessageProto(
|
||||
timestamp,
|
||||
numbers,
|
||||
message,
|
||||
recipients,
|
||||
messageProto,
|
||||
callback,
|
||||
silent,
|
||||
options = {}
|
||||
|
@ -388,8 +393,8 @@ MessageSender.prototype = {
|
|||
const rejections = textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||
if (rejections > 5) {
|
||||
throw new textsecure.SignedPreKeyRotationError(
|
||||
numbers,
|
||||
message.toArrayBuffer(),
|
||||
recipients,
|
||||
messageProto.toArrayBuffer(),
|
||||
timestamp
|
||||
);
|
||||
}
|
||||
|
@ -397,19 +402,27 @@ MessageSender.prototype = {
|
|||
const outgoing = new OutgoingMessage(
|
||||
this.server,
|
||||
timestamp,
|
||||
numbers,
|
||||
message,
|
||||
recipients,
|
||||
messageProto,
|
||||
silent,
|
||||
callback,
|
||||
options
|
||||
);
|
||||
|
||||
numbers.forEach(number => {
|
||||
this.queueJobForNumber(number, () => outgoing.sendToNumber(number));
|
||||
recipients.forEach(identifier => {
|
||||
this.queueJobForIdentifier(identifier, () =>
|
||||
outgoing.sendToIdentifier(identifier)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
sendMessageProtoAndWait(timestamp, numbers, message, silent, options = {}) {
|
||||
sendMessageProtoAndWait(
|
||||
timestamp,
|
||||
identifiers,
|
||||
messageProto,
|
||||
silent,
|
||||
options = {}
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = result => {
|
||||
if (result && result.errors && result.errors.length > 0) {
|
||||
|
@ -421,8 +434,8 @@ MessageSender.prototype = {
|
|||
|
||||
this.sendMessageProto(
|
||||
timestamp,
|
||||
numbers,
|
||||
message,
|
||||
identifiers,
|
||||
messageProto,
|
||||
callback,
|
||||
silent,
|
||||
options
|
||||
|
@ -430,7 +443,7 @@ MessageSender.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
sendIndividualProto(number, proto, timestamp, silent, options = {}) {
|
||||
sendIndividualProto(identifier, proto, timestamp, silent, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = res => {
|
||||
if (res && res.errors && res.errors.length > 0) {
|
||||
|
@ -441,7 +454,7 @@ MessageSender.prototype = {
|
|||
};
|
||||
this.sendMessageProto(
|
||||
timestamp,
|
||||
[number],
|
||||
[identifier],
|
||||
proto,
|
||||
callback,
|
||||
silent,
|
||||
|
@ -467,6 +480,7 @@ MessageSender.prototype = {
|
|||
encodedDataMessage,
|
||||
timestamp,
|
||||
destination,
|
||||
destinationUuid,
|
||||
expirationStartTimestamp,
|
||||
sentTo = [],
|
||||
unidentifiedDeliveries = [],
|
||||
|
@ -474,7 +488,9 @@ MessageSender.prototype = {
|
|||
options
|
||||
) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
|
||||
if (myDevice === 1 || myDevice === '1') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -488,6 +504,9 @@ MessageSender.prototype = {
|
|||
if (destination) {
|
||||
sentMessage.destination = destination;
|
||||
}
|
||||
if (destinationUuid) {
|
||||
sentMessage.destinationUuid = destinationUuid;
|
||||
}
|
||||
if (expirationStartTimestamp) {
|
||||
sentMessage.expirationStartTimestamp = expirationStartTimestamp;
|
||||
}
|
||||
|
@ -508,10 +527,16 @@ MessageSender.prototype = {
|
|||
// Though this field has 'unidenified' in the name, it should have entries for each
|
||||
// number we sent to.
|
||||
if (sentTo && sentTo.length) {
|
||||
sentMessage.unidentifiedStatus = sentTo.map(number => {
|
||||
sentMessage.unidentifiedStatus = sentTo.map(identifier => {
|
||||
const status = new textsecure.protobuf.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||
status.destination = number;
|
||||
status.unidentified = Boolean(unidentifiedLookup[number]);
|
||||
const conv = ConversationController.get(identifier);
|
||||
if (conv && conv.get('e164')) {
|
||||
status.destination = conv.get('e164');
|
||||
}
|
||||
if (conv && conv.get('uuid')) {
|
||||
status.destinationUuid = conv.get('uuid');
|
||||
}
|
||||
status.unidentified = Boolean(unidentifiedLookup[identifier]);
|
||||
return status;
|
||||
});
|
||||
}
|
||||
|
@ -523,7 +548,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
timestamp,
|
||||
silent,
|
||||
|
@ -552,6 +577,7 @@ MessageSender.prototype = {
|
|||
|
||||
sendRequestBlockSyncMessage(options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1 && myDevice !== '1') {
|
||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||
|
@ -563,7 +589,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -576,6 +602,7 @@ MessageSender.prototype = {
|
|||
|
||||
sendRequestConfigurationSyncMessage(options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1 && myDevice !== '1') {
|
||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||
|
@ -587,7 +614,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -600,6 +627,7 @@ MessageSender.prototype = {
|
|||
|
||||
sendRequestGroupSyncMessage(options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1 && myDevice !== '1') {
|
||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||
|
@ -611,7 +639,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -624,6 +652,8 @@ MessageSender.prototype = {
|
|||
|
||||
sendRequestContactSyncMessage(options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1 && myDevice !== '1') {
|
||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||
|
@ -635,7 +665,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -653,7 +683,8 @@ MessageSender.prototype = {
|
|||
// We don't want to send typing messages to our other devices, but we will
|
||||
// in the group case.
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
if (recipientId && myNumber === recipientId) {
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
if (recipientId && (myNumber === recipientId || myUuid === recipientId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -662,7 +693,7 @@ MessageSender.prototype = {
|
|||
}
|
||||
|
||||
const recipients = groupId
|
||||
? _.without(groupNumbers, myNumber)
|
||||
? _.without(groupNumbers, myNumber, myUuid)
|
||||
: [recipientId];
|
||||
const groupIdBuffer = groupId
|
||||
? window.Signal.Crypto.fromEncodedBinaryToArrayBuffer(groupId)
|
||||
|
@ -694,10 +725,14 @@ MessageSender.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
sendDeliveryReceipt(recipientId, timestamps, options) {
|
||||
sendDeliveryReceipt(recipientE164, recipientUuid, timestamps, options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myNumber === recipientId && (myDevice === 1 || myDevice === '1')) {
|
||||
if (
|
||||
(myNumber === recipientE164 || myUuid === recipientUuid) &&
|
||||
(myDevice === 1 || myDevice === '1')
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -710,7 +745,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
recipientId,
|
||||
recipientUuid || recipientE164,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -718,7 +753,7 @@ MessageSender.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
sendReadReceipts(sender, timestamps, options) {
|
||||
sendReadReceipts(senderE164, senderUuid, timestamps, options) {
|
||||
const receiptMessage = new textsecure.protobuf.ReceiptMessage();
|
||||
receiptMessage.type = textsecure.protobuf.ReceiptMessage.Type.READ;
|
||||
receiptMessage.timestamp = timestamps;
|
||||
|
@ -728,7 +763,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
sender,
|
||||
senderUuid || senderE164,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -737,6 +772,7 @@ MessageSender.prototype = {
|
|||
},
|
||||
syncReadMessages(reads, options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1 && myDevice !== '1') {
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
@ -745,6 +781,7 @@ MessageSender.prototype = {
|
|||
const read = new textsecure.protobuf.SyncMessage.Read();
|
||||
read.timestamp = reads[i].timestamp;
|
||||
read.sender = reads[i].sender;
|
||||
|
||||
syncMessage.read.push(read);
|
||||
}
|
||||
const contentMessage = new textsecure.protobuf.Content();
|
||||
|
@ -752,7 +789,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -763,8 +800,9 @@ MessageSender.prototype = {
|
|||
return Promise.resolve();
|
||||
},
|
||||
|
||||
async syncViewOnceOpen(sender, timestamp, options) {
|
||||
async syncViewOnceOpen(sender, senderUuid, timestamp, options) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
if (myDevice === 1 || myDevice === '1') {
|
||||
return null;
|
||||
|
@ -774,6 +812,7 @@ MessageSender.prototype = {
|
|||
|
||||
const viewOnceOpen = new textsecure.protobuf.SyncMessage.ViewOnceOpen();
|
||||
viewOnceOpen.sender = sender;
|
||||
viewOnceOpen.senderUuid = senderUuid;
|
||||
viewOnceOpen.timestamp = timestamp;
|
||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||
|
||||
|
@ -782,7 +821,7 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
|
@ -797,6 +836,7 @@ MessageSender.prototype = {
|
|||
}
|
||||
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const ENUM = textsecure.protobuf.SyncMessage.StickerPackOperation.Type;
|
||||
|
||||
const packOperations = operations.map(item => {
|
||||
|
@ -818,15 +858,23 @@ MessageSender.prototype = {
|
|||
|
||||
const silent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
contentMessage,
|
||||
Date.now(),
|
||||
silent,
|
||||
options
|
||||
);
|
||||
},
|
||||
syncVerification(destination, state, identityKey, options) {
|
||||
|
||||
syncVerification(
|
||||
destinationE164,
|
||||
destinationUuid,
|
||||
state,
|
||||
identityKey,
|
||||
options
|
||||
) {
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const myDevice = textsecure.storage.user.getDeviceId();
|
||||
const now = Date.now();
|
||||
|
||||
|
@ -850,7 +898,7 @@ MessageSender.prototype = {
|
|||
// We want the NullMessage to look like a normal outgoing message; not silent
|
||||
const silent = false;
|
||||
const promise = this.sendIndividualProto(
|
||||
destination,
|
||||
destinationUuid || destinationE164,
|
||||
contentMessage,
|
||||
now,
|
||||
silent,
|
||||
|
@ -860,7 +908,12 @@ MessageSender.prototype = {
|
|||
return promise.then(() => {
|
||||
const verified = new textsecure.protobuf.Verified();
|
||||
verified.state = state;
|
||||
verified.destination = destination;
|
||||
if (destinationE164) {
|
||||
verified.destination = destinationE164;
|
||||
}
|
||||
if (destinationUuid) {
|
||||
verified.destinationUuid = destinationUuid;
|
||||
}
|
||||
verified.identityKey = identityKey;
|
||||
verified.nullMessage = nullMessage.padding;
|
||||
|
||||
|
@ -872,7 +925,7 @@ MessageSender.prototype = {
|
|||
|
||||
const innerSilent = true;
|
||||
return this.sendIndividualProto(
|
||||
myNumber,
|
||||
myUuid || myNumber,
|
||||
secondMessage,
|
||||
now,
|
||||
innerSilent,
|
||||
|
@ -881,13 +934,22 @@ MessageSender.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
sendGroupProto(providedNumbers, proto, timestamp = Date.now(), options = {}) {
|
||||
const me = textsecure.storage.user.getNumber();
|
||||
const numbers = providedNumbers.filter(number => number !== me);
|
||||
if (numbers.length === 0) {
|
||||
sendGroupProto(
|
||||
providedIdentifiers,
|
||||
proto,
|
||||
timestamp = Date.now(),
|
||||
options = {}
|
||||
) {
|
||||
const myE164 = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const identifiers = providedIdentifiers.filter(
|
||||
id => id !== myE164 && id !== myUuid
|
||||
);
|
||||
|
||||
if (identifiers.length === 0) {
|
||||
return Promise.resolve({
|
||||
successfulNumbers: [],
|
||||
failoverNumbers: [],
|
||||
successfulIdentifiers: [],
|
||||
failoverIdentifiers: [],
|
||||
errors: [],
|
||||
unidentifiedDeliveries: [],
|
||||
dataMessage: proto.toArrayBuffer(),
|
||||
|
@ -907,7 +969,7 @@ MessageSender.prototype = {
|
|||
|
||||
this.sendMessageProto(
|
||||
timestamp,
|
||||
numbers,
|
||||
providedIdentifiers,
|
||||
proto,
|
||||
callback,
|
||||
silent,
|
||||
|
@ -917,7 +979,7 @@ MessageSender.prototype = {
|
|||
},
|
||||
|
||||
async getMessageProto(
|
||||
number,
|
||||
destination,
|
||||
body,
|
||||
attachments,
|
||||
quote,
|
||||
|
@ -930,7 +992,8 @@ MessageSender.prototype = {
|
|||
flags
|
||||
) {
|
||||
const attributes = {
|
||||
recipients: [number],
|
||||
recipients: [destination],
|
||||
destination,
|
||||
body,
|
||||
timestamp,
|
||||
attachments,
|
||||
|
@ -958,8 +1021,8 @@ MessageSender.prototype = {
|
|||
return message.toArrayBuffer();
|
||||
},
|
||||
|
||||
sendMessageToNumber(
|
||||
number,
|
||||
sendMessageToIdentifier(
|
||||
identifier,
|
||||
messageText,
|
||||
attachments,
|
||||
quote,
|
||||
|
@ -973,7 +1036,7 @@ MessageSender.prototype = {
|
|||
) {
|
||||
return this.sendMessage(
|
||||
{
|
||||
recipients: [number],
|
||||
recipients: [identifier],
|
||||
body: messageText,
|
||||
timestamp,
|
||||
attachments,
|
||||
|
@ -988,7 +1051,7 @@ MessageSender.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
resetSession(number, timestamp, options) {
|
||||
resetSession(identifier, timestamp, options) {
|
||||
window.log.info('resetting secure session');
|
||||
const silent = false;
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
|
@ -1017,14 +1080,14 @@ MessageSender.prototype = {
|
|||
)
|
||||
);
|
||||
|
||||
const sendToContactPromise = deleteAllSessions(number)
|
||||
const sendToContactPromise = deleteAllSessions(identifier)
|
||||
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
||||
.then(() => {
|
||||
window.log.info(
|
||||
'finished closing local sessions, now sending to contact'
|
||||
);
|
||||
return this.sendIndividualProto(
|
||||
number,
|
||||
identifier,
|
||||
proto,
|
||||
timestamp,
|
||||
silent,
|
||||
|
@ -1032,14 +1095,15 @@ MessageSender.prototype = {
|
|||
).catch(logError('resetSession/sendToContact error:'));
|
||||
})
|
||||
.then(() =>
|
||||
deleteAllSessions(number).catch(
|
||||
deleteAllSessions(identifier).catch(
|
||||
logError('resetSession/deleteAllSessions2 error:')
|
||||
)
|
||||
);
|
||||
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
// We already sent the reset session to our other devices in the code above!
|
||||
if (number === myNumber) {
|
||||
if (identifier === myNumber || identifier === myUuid) {
|
||||
return sendToContactPromise;
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1111,7 @@ MessageSender.prototype = {
|
|||
const sendSyncPromise = this.sendSyncMessage(
|
||||
buffer,
|
||||
timestamp,
|
||||
number,
|
||||
identifier,
|
||||
null,
|
||||
[],
|
||||
[],
|
||||
|
@ -1059,7 +1123,7 @@ MessageSender.prototype = {
|
|||
|
||||
async sendMessageToGroup(
|
||||
groupId,
|
||||
groupNumbers,
|
||||
recipients,
|
||||
messageText,
|
||||
attachments,
|
||||
quote,
|
||||
|
@ -1071,10 +1135,10 @@ MessageSender.prototype = {
|
|||
profileKey,
|
||||
options
|
||||
) {
|
||||
const me = textsecure.storage.user.getNumber();
|
||||
const numbers = groupNumbers.filter(number => number !== me);
|
||||
const myE164 = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getNumber();
|
||||
const attrs = {
|
||||
recipients: numbers,
|
||||
recipients: recipients.filter(r => r !== myE164 && r !== myUuid),
|
||||
body: messageText,
|
||||
timestamp,
|
||||
attachments,
|
||||
|
@ -1090,10 +1154,10 @@ MessageSender.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
if (numbers.length === 0) {
|
||||
if (recipients.length === 0) {
|
||||
return Promise.resolve({
|
||||
successfulNumbers: [],
|
||||
failoverNumbers: [],
|
||||
successfulIdentifiers: [],
|
||||
failoverIdentifiers: [],
|
||||
errors: [],
|
||||
unidentifiedDeliveries: [],
|
||||
dataMessage: await this.getMessageProtoObj(attrs),
|
||||
|
@ -1103,19 +1167,20 @@ MessageSender.prototype = {
|
|||
return this.sendMessage(attrs, options);
|
||||
},
|
||||
|
||||
createGroup(targetNumbers, id, name, avatar, options) {
|
||||
createGroup(targetIdentifiers, id, name, avatar, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(id);
|
||||
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||
proto.group.members = targetNumbers;
|
||||
// TODO
|
||||
proto.group.members = targetIdentifiers;
|
||||
proto.group.name = name;
|
||||
|
||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||
proto.group.avatar = attachment;
|
||||
return this.sendGroupProto(
|
||||
targetNumbers,
|
||||
targetIdentifiers,
|
||||
proto,
|
||||
Date.now(),
|
||||
options
|
||||
|
@ -1123,19 +1188,19 @@ MessageSender.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
updateGroup(groupId, name, avatar, targetNumbers, options) {
|
||||
updateGroup(groupId, name, avatar, targetIdentifiers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||
proto.group.name = name;
|
||||
proto.group.members = targetNumbers;
|
||||
proto.group.members = targetIdentifiers;
|
||||
|
||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||
proto.group.avatar = attachment;
|
||||
return this.sendGroupProto(
|
||||
targetNumbers,
|
||||
targetIdentifiers,
|
||||
proto,
|
||||
Date.now(),
|
||||
options
|
||||
|
@ -1143,58 +1208,61 @@ MessageSender.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
addNumberToGroup(groupId, newNumbers, options) {
|
||||
addIdentifierToGroup(groupId, newIdentifiers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||
proto.group.members = newNumbers;
|
||||
return this.sendGroupProto(newNumbers, proto, Date.now(), options);
|
||||
proto.group.members = newIdentifiers;
|
||||
return this.sendGroupProto(newIdentifiers, proto, Date.now(), options);
|
||||
},
|
||||
|
||||
setGroupName(groupId, name, groupNumbers, options) {
|
||||
setGroupName(groupId, name, groupIdentifiers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||
proto.group.name = name;
|
||||
proto.group.members = groupNumbers;
|
||||
proto.group.members = groupIdentifiers;
|
||||
|
||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
||||
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||
},
|
||||
|
||||
setGroupAvatar(groupId, avatar, groupNumbers, options) {
|
||||
setGroupAvatar(groupId, avatar, groupIdentifiers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||
proto.group.members = groupNumbers;
|
||||
proto.group.members = groupIdentifiers;
|
||||
|
||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||
proto.group.avatar = attachment;
|
||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
||||
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||
});
|
||||
},
|
||||
|
||||
leaveGroup(groupId, groupNumbers, options) {
|
||||
leaveGroup(groupId, groupIdentifiers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
|
||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
||||
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||
},
|
||||
async sendExpirationTimerUpdateToGroup(
|
||||
groupId,
|
||||
groupNumbers,
|
||||
groupIdentifiers,
|
||||
expireTimer,
|
||||
timestamp,
|
||||
profileKey,
|
||||
options
|
||||
) {
|
||||
const me = textsecure.storage.user.getNumber();
|
||||
const numbers = groupNumbers.filter(number => number !== me);
|
||||
const myNumber = textsecure.storage.user.getNumber();
|
||||
const myUuid = textsecure.storage.user.getUuid();
|
||||
const recipients = groupIdentifiers.filter(
|
||||
identifier => identifier !== myNumber && identifier !== myUuid
|
||||
);
|
||||
const attrs = {
|
||||
recipients: numbers,
|
||||
recipients,
|
||||
timestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
|
@ -1205,10 +1273,10 @@ MessageSender.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
if (numbers.length === 0) {
|
||||
if (recipients.length === 0) {
|
||||
return Promise.resolve({
|
||||
successfulNumbers: [],
|
||||
failoverNumbers: [],
|
||||
successfulIdentifiers: [],
|
||||
failoverIdentifiers: [],
|
||||
errors: [],
|
||||
unidentifiedDeliveries: [],
|
||||
dataMessage: await this.getMessageProtoObj(attrs),
|
||||
|
@ -1217,8 +1285,8 @@ MessageSender.prototype = {
|
|||
|
||||
return this.sendMessage(attrs, options);
|
||||
},
|
||||
sendExpirationTimerUpdateToNumber(
|
||||
number,
|
||||
sendExpirationTimerUpdateToIdentifier(
|
||||
identifier,
|
||||
expireTimer,
|
||||
timestamp,
|
||||
profileKey,
|
||||
|
@ -1226,7 +1294,7 @@ MessageSender.prototype = {
|
|||
) {
|
||||
return this.sendMessage(
|
||||
{
|
||||
recipients: [number],
|
||||
recipients: [identifier],
|
||||
timestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
|
@ -1245,7 +1313,7 @@ window.textsecure = window.textsecure || {};
|
|||
textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
||||
const sender = new MessageSender(username, password);
|
||||
|
||||
this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(
|
||||
this.sendExpirationTimerUpdateToIdentifier = sender.sendExpirationTimerUpdateToIdentifier.bind(
|
||||
sender
|
||||
);
|
||||
this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup.bind(
|
||||
|
@ -1264,14 +1332,14 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
|||
sender
|
||||
);
|
||||
|
||||
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
||||
this.sendMessageToIdentifier = sender.sendMessageToIdentifier.bind(sender);
|
||||
this.sendMessage = sender.sendMessage.bind(sender);
|
||||
this.resetSession = sender.resetSession.bind(sender);
|
||||
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
|
||||
this.sendTypingMessage = sender.sendTypingMessage.bind(sender);
|
||||
this.createGroup = sender.createGroup.bind(sender);
|
||||
this.updateGroup = sender.updateGroup.bind(sender);
|
||||
this.addNumberToGroup = sender.addNumberToGroup.bind(sender);
|
||||
this.addIdentifierToGroup = sender.addIdentifierToGroup.bind(sender);
|
||||
this.setGroupName = sender.setGroupName.bind(sender);
|
||||
this.setGroupAvatar = sender.setGroupAvatar.bind(sender);
|
||||
this.leaveGroup = sender.leaveGroup.bind(sender);
|
||||
|
|
|
@ -16,13 +16,33 @@
|
|||
}
|
||||
},
|
||||
|
||||
setUuidAndDeviceId(uuid, deviceId) {
|
||||
textsecure.storage.put('uuid_id', `${uuid}.${deviceId}`);
|
||||
},
|
||||
|
||||
getNumber() {
|
||||
const numberId = textsecure.storage.get('number_id');
|
||||
if (numberId === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(numberId)[0];
|
||||
},
|
||||
|
||||
getUuid() {
|
||||
const uuid = textsecure.storage.get('uuid_id');
|
||||
if (uuid === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(uuid)[0];
|
||||
},
|
||||
|
||||
getDeviceId() {
|
||||
return this._getDeviceIdFromUuid() || this._getDeviceIdFromNumber();
|
||||
},
|
||||
|
||||
_getDeviceIdFromUuid() {
|
||||
const uuid = textsecure.storage.get('uuid_id');
|
||||
if (uuid === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(uuid)[1];
|
||||
},
|
||||
|
||||
_getDeviceIdFromNumber() {
|
||||
const numberId = textsecure.storage.get('number_id');
|
||||
if (numberId === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(numberId)[1];
|
||||
|
|
|
@ -13,6 +13,7 @@ describe('ContactBuffer', () => {
|
|||
const contactInfo = new textsecure.protobuf.ContactDetails({
|
||||
name: 'Zero Cool',
|
||||
number: '+10000000000',
|
||||
uuid: '7198E1BD-1293-452A-A098-F982FF201902',
|
||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||
});
|
||||
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||
|
@ -37,6 +38,7 @@ describe('ContactBuffer', () => {
|
|||
count += 1;
|
||||
assert.strictEqual(contact.name, 'Zero Cool');
|
||||
assert.strictEqual(contact.number, '+10000000000');
|
||||
assert.strictEqual(contact.uuid, '7198e1bd-1293-452a-a098-f982ff201902');
|
||||
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
||||
assert.strictEqual(contact.avatar.length, 255);
|
||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
||||
|
@ -63,7 +65,13 @@ describe('GroupBuffer', () => {
|
|||
const groupInfo = new textsecure.protobuf.GroupDetails({
|
||||
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
||||
name: 'Hackers',
|
||||
members: ['cereal', 'burn', 'phreak', 'joey'],
|
||||
membersE164: ['cereal', 'burn', 'phreak', 'joey'],
|
||||
members: [
|
||||
{ uuid: '3EA23646-92E8-4604-8833-6388861971C1', e164: 'cereal' },
|
||||
{ uuid: 'B8414169-7149-4736-8E3B-477191931301', e164: 'burn' },
|
||||
{ uuid: '64C97B95-A782-4E1E-BBCC-5A4ACE8d71f6', e164: 'phreak' },
|
||||
{ uuid: 'CA334652-C35B-4FDC-9CC7-5F2060C771EE', e164: 'joey' },
|
||||
],
|
||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||
});
|
||||
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||
|
@ -91,7 +99,21 @@ describe('GroupBuffer', () => {
|
|||
group.id.toArrayBuffer(),
|
||||
new Uint8Array([1, 3, 3, 7]).buffer
|
||||
);
|
||||
assert.sameMembers(group.members, ['cereal', 'burn', 'phreak', 'joey']);
|
||||
assert.sameMembers(group.membersE164, [
|
||||
'cereal',
|
||||
'burn',
|
||||
'phreak',
|
||||
'joey',
|
||||
]);
|
||||
assert.sameDeepMembers(
|
||||
group.members.map(({ uuid, e164 }) => ({ uuid, e164 })),
|
||||
[
|
||||
{ uuid: '3ea23646-92e8-4604-8833-6388861971c1', e164: 'cereal' },
|
||||
{ uuid: 'b8414169-7149-4736-8e3b-477191931301', e164: 'burn' },
|
||||
{ uuid: '64c97b95-a782-4e1e-bbcc-5a4ace8d71f6', e164: 'phreak' },
|
||||
{ uuid: 'ca334652-c35b-4fdc-9cc7-5f2060c771ee', e164: 'joey' },
|
||||
]
|
||||
);
|
||||
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
||||
assert.strictEqual(group.avatar.length, 255);
|
||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
window.setImmediate = window.nodeSetImmediate;
|
||||
|
||||
const getKeysForNumberMap = {};
|
||||
const getKeysForIdentifierMap = {};
|
||||
const messagesSentMap = {};
|
||||
|
||||
const fakeCall = () => Promise.resolve();
|
||||
|
@ -10,7 +10,7 @@ const fakeAPI = {
|
|||
getAttachment: fakeCall,
|
||||
getAvatar: fakeCall,
|
||||
getDevices: fakeCall,
|
||||
// getKeysForNumber: fakeCall,
|
||||
// getKeysForIdentifier : fakeCall,
|
||||
getMessageSocket: fakeCall,
|
||||
getMyKeys: fakeCall,
|
||||
getProfile: fakeCall,
|
||||
|
@ -22,13 +22,13 @@ const fakeAPI = {
|
|||
// sendMessages: fakeCall,
|
||||
setSignedPreKey: fakeCall,
|
||||
|
||||
getKeysForNumber(number) {
|
||||
const res = getKeysForNumberMap[number];
|
||||
getKeysForIdentifier(number) {
|
||||
const res = getKeysForIdentifierMap[number];
|
||||
if (res !== undefined) {
|
||||
delete getKeysForNumberMap[number];
|
||||
delete getKeysForIdentifierMap[number];
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
throw new Error('getKeysForNumber of unknown/used number');
|
||||
throw new Error('getKeysForIdentfier of unknown/used number');
|
||||
},
|
||||
|
||||
sendMessages(destination, messageArray) {
|
||||
|
|
|
@ -4,6 +4,12 @@ function SignalProtocolStore() {
|
|||
|
||||
SignalProtocolStore.prototype = {
|
||||
Direction: { SENDING: 1, RECEIVING: 2 },
|
||||
VerifiedStatus: {
|
||||
DEFAULT: 0,
|
||||
VERIFIED: 1,
|
||||
UNVERIFIED: 2,
|
||||
},
|
||||
|
||||
getIdentityKeyPair() {
|
||||
return Promise.resolve(this.get('identityKey'));
|
||||
},
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
||||
<script type="text/javascript" src="../errors.js" data-cover></script>
|
||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
||||
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
||||
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>
|
||||
|
@ -34,6 +33,16 @@
|
|||
<script type="text/javascript" src="../account_manager.js" data-cover></script>
|
||||
<script type="text/javascript" src="../contacts_parser.js" data-cover></script>
|
||||
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
||||
<script type="text/javascript" src="../storage/user.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/libphonenumber-util.js"></script>
|
||||
<script type="text/javascript" src="../../js/components.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/signal_protocol_store.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/storage.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/models/messages.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/models/conversations.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../js/conversation_controller.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="errors_test.js"></script>
|
||||
<script type="text/javascript" src="helpers_test.js"></script>
|
||||
|
|
|
@ -4,12 +4,14 @@ describe('MessageReceiver', () => {
|
|||
textsecure.storage.impl = new SignalProtocolStore();
|
||||
const { WebSocket } = window;
|
||||
const number = '+19999999999';
|
||||
const uuid = 'AAAAAAAA-BBBB-4CCC-9DDD-EEEEEEEEEEEE';
|
||||
const deviceId = 1;
|
||||
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||
|
||||
before(() => {
|
||||
window.WebSocket = MockSocket;
|
||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
||||
textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
|
||||
textsecure.storage.put('password', 'password');
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
});
|
||||
|
@ -21,6 +23,7 @@ describe('MessageReceiver', () => {
|
|||
const attrs = {
|
||||
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
||||
source: number,
|
||||
sourceUuid: uuid,
|
||||
sourceDevice: deviceId,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
@ -72,7 +75,7 @@ describe('MessageReceiver', () => {
|
|||
it('connects', done => {
|
||||
const mockServer = new MockServer(
|
||||
`ws://localhost:8080/v1/websocket/?login=${encodeURIComponent(
|
||||
number
|
||||
uuid
|
||||
)}.1&password=password`
|
||||
);
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
/* global libsignal, textsecure */
|
||||
/* global libsignal, textsecure, storage, ConversationController */
|
||||
|
||||
describe('SignalProtocolStore', () => {
|
||||
before(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
// debugger;
|
||||
const store = textsecure.storage.protocol;
|
||||
const identifier = '+5558675309';
|
||||
const identityKey = {
|
||||
|
@ -14,6 +12,14 @@ describe('SignalProtocolStore', () => {
|
|||
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||
privKey: libsignal.crypto.getRandomBytes(32),
|
||||
};
|
||||
before(async () => {
|
||||
localStorage.clear();
|
||||
ConversationController.reset();
|
||||
// store.hydrateCaches();
|
||||
await storage.fetch();
|
||||
await ConversationController.load();
|
||||
await ConversationController.getOrCreateAndWait(identifier, 'private');
|
||||
});
|
||||
it('retrieves my registration id', async () => {
|
||||
store.put('registrationId', 1337);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue