Multidevice support for medium groups
This commit is contained in:
parent
3561ac49c0
commit
2a0130ff04
|
@ -404,29 +404,29 @@ module.exports = {
|
|||
)
|
||||
).should.eventually.be.true;
|
||||
|
||||
await Promise.all(others.map(async app => {
|
||||
|
||||
// next check that other members have been invited and have the group in their conversations
|
||||
await app.client.waitForExist(
|
||||
ConversationPage.rowOpenGroupConversationName(
|
||||
this.VALID_CLOSED_GROUP_NAME1
|
||||
),
|
||||
6000
|
||||
await Promise.all(
|
||||
others.map(async otherApp => {
|
||||
// next check that other members have been invited and have the group in their conversations
|
||||
await otherApp.client.waitForExist(
|
||||
ConversationPage.rowOpenGroupConversationName(
|
||||
this.VALID_CLOSED_GROUP_NAME1
|
||||
),
|
||||
6000
|
||||
);
|
||||
// open the closed group conversation on otherApp
|
||||
await otherApp.client
|
||||
.element(ConversationPage.conversationButtonSection)
|
||||
.click();
|
||||
await this.timeout(500);
|
||||
await otherApp.client
|
||||
.element(
|
||||
ConversationPage.rowOpenGroupConversationName(
|
||||
this.VALID_CLOSED_GROUP_NAME1
|
||||
)
|
||||
)
|
||||
.click();
|
||||
})
|
||||
);
|
||||
// open the closed group conversation on app2
|
||||
await app.client
|
||||
.element(ConversationPage.conversationButtonSection)
|
||||
.click();
|
||||
await this.timeout(500);
|
||||
await app.client
|
||||
.element(
|
||||
ConversationPage.rowOpenGroupConversationName(
|
||||
this.VALID_CLOSED_GROUP_NAME1
|
||||
)
|
||||
)
|
||||
.click();
|
||||
|
||||
}));
|
||||
},
|
||||
|
||||
async linkApp2ToApp(app1, app2) {
|
||||
|
|
|
@ -64,7 +64,9 @@ module.exports = {
|
|||
'Enter a group name'
|
||||
),
|
||||
createClosedGroupMemberItem: commonPage.divWithClass('session-member-item'),
|
||||
createClosedGroupSealedSenderToggle: commonPage.divWithClass('session-toggle'),
|
||||
createClosedGroupSealedSenderToggle: commonPage.divWithClass(
|
||||
'session-toggle'
|
||||
),
|
||||
createClosedGroupMemberItemSelected: commonPage.divWithClass(
|
||||
'session-member-item selected'
|
||||
),
|
||||
|
|
|
@ -746,17 +746,9 @@
|
|||
identityKeys.privKey
|
||||
);
|
||||
|
||||
// Constructing a "create group" message
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
const primary = window.storage.get('primaryDevicePubKey');
|
||||
|
||||
const groupUpdate = new textsecure.protobuf.MediumGroupUpdate();
|
||||
|
||||
groupUpdate.groupId = groupId;
|
||||
groupUpdate.groupSecretKey = groupSecretKeyHex;
|
||||
groupUpdate.senderKey = senderKey;
|
||||
groupUpdate.members = [ourIdentity, ...members];
|
||||
groupUpdate.groupName = groupName;
|
||||
proto.mediumGroupUpdate = groupUpdate;
|
||||
const allMembers = [primary, ...members];
|
||||
|
||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||
id: groupId,
|
||||
|
@ -768,11 +760,14 @@
|
|||
ev.groupDetails = {
|
||||
id: groupId,
|
||||
name: groupName,
|
||||
members: groupUpdate.members,
|
||||
recipients: groupUpdate.members,
|
||||
members: allMembers,
|
||||
recipients: allMembers,
|
||||
active: true,
|
||||
expireTimer: 0,
|
||||
avatar: '',
|
||||
secretKey: identityKeys.privKey,
|
||||
senderKey,
|
||||
is_medium_group: true,
|
||||
};
|
||||
|
||||
ev.confirm = () => {};
|
||||
|
@ -786,13 +781,14 @@
|
|||
|
||||
convo.updateGroup(ev.groupDetails);
|
||||
|
||||
convo.setFriendRequestStatus(
|
||||
window.friends.friendRequestStatusEnum.friends
|
||||
);
|
||||
|
||||
appView.openConversation(groupId, {});
|
||||
|
||||
// Subscribe to this group id
|
||||
messageReceiver.pollForAdditionalId(groupId);
|
||||
|
||||
// TODO: include ourselves so that our lined devices work as well!
|
||||
await textsecure.messaging.updateMediumGroup(members, proto);
|
||||
};
|
||||
|
||||
window.doCreateGroup = async (groupName, members) => {
|
||||
|
@ -1911,6 +1907,7 @@
|
|||
members: details.members,
|
||||
color: details.color,
|
||||
type: 'group',
|
||||
is_medium_group: details.is_medium_group || false,
|
||||
};
|
||||
|
||||
if (details.active) {
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
clipboard,
|
||||
BlockedNumberController,
|
||||
lokiPublicChatAPI,
|
||||
JobQueue
|
||||
JobQueue,
|
||||
StringView
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -2289,12 +2290,41 @@
|
|||
group_update: groupUpdate,
|
||||
});
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
message.set({ id });
|
||||
const messageId = await window.Signal.Data.saveMessage(
|
||||
message.attributes,
|
||||
{
|
||||
Message: Whisper.Message,
|
||||
}
|
||||
);
|
||||
message.set({ id: messageId });
|
||||
|
||||
const options = this.getSendOptions();
|
||||
|
||||
if (groupUpdate.is_medium_group) {
|
||||
// Constructing a "create group" message
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
|
||||
const mgUpdate = new textsecure.protobuf.MediumGroupUpdate();
|
||||
|
||||
const { id, name, secretKey, senderKey, members } = groupUpdate;
|
||||
|
||||
mgUpdate.type = textsecure.protobuf.MediumGroupUpdate.Type.NEW_GROUP;
|
||||
mgUpdate.groupId = id;
|
||||
mgUpdate.groupSecretKey = secretKey;
|
||||
mgUpdate.senderKey = new textsecure.protobuf.SenderKey({
|
||||
chainKey: senderKey,
|
||||
keyIdx: 0,
|
||||
});
|
||||
mgUpdate.members = members.map(pkHex =>
|
||||
StringView.hexToArrayBuffer(pkHex)
|
||||
);
|
||||
mgUpdate.groupName = name;
|
||||
proto.mediumGroupUpdate = mgUpdate;
|
||||
|
||||
await textsecure.messaging.updateMediumGroup(members, proto);
|
||||
return;
|
||||
}
|
||||
|
||||
message.send(
|
||||
this.wrapSend(
|
||||
textsecure.messaging.updateGroup(
|
||||
|
|
|
@ -1477,7 +1477,8 @@
|
|||
if (!this.isFriendRequest()) {
|
||||
const c = this.getConversation();
|
||||
// Don't bother sending sync messages to public chats
|
||||
if (c && !c.isPublic()) {
|
||||
// or groups with sender keys
|
||||
if (c && !c.isPublic() && !c.get('is_medium_group')) {
|
||||
this.sendSyncMessage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
dcodeIO,
|
||||
libloki,
|
||||
log,
|
||||
crypto
|
||||
crypto,
|
||||
textsecure
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -39,9 +40,7 @@ async function saveSenderKeysInner(
|
|||
}
|
||||
|
||||
// Save somebody else's key
|
||||
async function saveSenderKeys(groupId, senderIdentity, chainKey) {
|
||||
// New key, so index 0
|
||||
const keyIdx = 0;
|
||||
async function saveSenderKeys(groupId, senderIdentity, chainKey, keyIdx) {
|
||||
const messageKeys = {};
|
||||
await saveSenderKeysInner(
|
||||
groupId,
|
||||
|
@ -133,7 +132,7 @@ async function advanceRatchet(groupId, senderIdentity, idx) {
|
|||
log.error(
|
||||
`Could not find ratchet for groupId ${groupId} sender: ${senderIdentity}`
|
||||
);
|
||||
return null;
|
||||
throw new textsecure.SenderKeyMissing(senderIdentity);
|
||||
}
|
||||
|
||||
// Normally keyIdx will be 1 behind, in which case we stepRatchet one time only
|
||||
|
@ -179,6 +178,7 @@ async function advanceRatchet(groupId, senderIdentity, idx) {
|
|||
break;
|
||||
} else if (nextKeyIdx > idx) {
|
||||
log.error('Developer error: nextKeyIdx > idx');
|
||||
return null;
|
||||
} else {
|
||||
// Store keys for skipped nextKeyIdx, we might need them to decrypt
|
||||
// messages that arrive out-of-order
|
||||
|
@ -289,9 +289,16 @@ async function encryptWithSenderKeyInner(plaintext, groupId, ourIdentity) {
|
|||
return { ciphertext, keyIdx };
|
||||
}
|
||||
|
||||
async function getSenderKeys(groupId, senderIdentity) {
|
||||
const { chainKey, keyIdx } = await loadChainKey(groupId, senderIdentity);
|
||||
|
||||
return { chainKey, keyIdx };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createSenderKeyForGroup,
|
||||
encryptWithSenderKey,
|
||||
decryptWithSenderKey,
|
||||
saveSenderKeys,
|
||||
getSenderKeys,
|
||||
};
|
||||
|
|
|
@ -220,6 +220,7 @@
|
|||
});
|
||||
return syncMessage;
|
||||
}
|
||||
|
||||
function createGroupSyncProtoMessage(sessionGroup) {
|
||||
// We are getting a single open group here
|
||||
|
||||
|
|
|
@ -263,6 +263,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
function SenderKeyMissing(senderIdentity) {
|
||||
this.name = 'SenderKeyMissing';
|
||||
this.senderIdentity = senderIdentity;
|
||||
|
||||
Error.call(this, this.name);
|
||||
|
||||
// Maintains proper stack trace, where our error was thrown (only available on V8)
|
||||
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
}
|
||||
}
|
||||
|
||||
window.textsecure.UnregisteredUserError = UnregisteredUserError;
|
||||
window.textsecure.SendMessageNetworkError = SendMessageNetworkError;
|
||||
window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError;
|
||||
|
@ -282,4 +295,5 @@
|
|||
window.textsecure.TimestampError = TimestampError;
|
||||
window.textsecure.PublicChatError = PublicChatError;
|
||||
window.textsecure.PublicTokenError = PublicTokenError;
|
||||
window.textsecure.SenderKeyMissing = SenderKeyMissing;
|
||||
})();
|
||||
|
|
|
@ -849,6 +849,22 @@ MessageReceiver.prototype.extend({
|
|||
return promise
|
||||
.then(plaintext => this.postDecrypt(envelope, plaintext))
|
||||
.catch(error => {
|
||||
if (error && error instanceof textsecure.SenderKeyMissing) {
|
||||
const groupId = envelope.source;
|
||||
const { senderIdentity } = error;
|
||||
|
||||
log.info(
|
||||
'Requesting missing key for identity: ',
|
||||
senderIdentity,
|
||||
'groupId: ',
|
||||
groupId
|
||||
);
|
||||
|
||||
textsecure.messaging.requestSenderKeys(senderIdentity, groupId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let errorToThrow = error;
|
||||
|
||||
const noSession =
|
||||
|
@ -876,7 +892,7 @@ MessageReceiver.prototype.extend({
|
|||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
|
||||
const returnError = () => Promise.reject(errorToThrow);
|
||||
return this.dispatchAndWait(ev).then(returnError, returnError);
|
||||
this.dispatchAndWait(ev).then(returnError, returnError);
|
||||
});
|
||||
},
|
||||
async decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address) {
|
||||
|
@ -900,7 +916,7 @@ MessageReceiver.prototype.extend({
|
|||
},
|
||||
// handle a SYNC message for a message
|
||||
// sent by another device
|
||||
handleSentMessage(envelope, sentContainer, msg) {
|
||||
async handleSentMessage(envelope, sentContainer, msg) {
|
||||
const {
|
||||
destination,
|
||||
timestamp,
|
||||
|
@ -908,41 +924,63 @@ MessageReceiver.prototype.extend({
|
|||
unidentifiedStatus,
|
||||
} = sentContainer;
|
||||
|
||||
let p = Promise.resolve();
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
||||
p = this.handleEndSession(destination);
|
||||
await this.handleEndSession(destination);
|
||||
}
|
||||
return p.then(() =>
|
||||
this.processDecrypted(envelope, msg).then(message => {
|
||||
const primaryDevicePubKey = window.storage.get('primaryDevicePubKey');
|
||||
|
||||
// handle profileKey and avatar updates
|
||||
if (envelope.source === primaryDevicePubKey) {
|
||||
const { profileKey, profile } = message;
|
||||
const primaryConversation = ConversationController.get(
|
||||
primaryDevicePubKey
|
||||
);
|
||||
if (profile) {
|
||||
this.updateProfile(primaryConversation, profile, profileKey);
|
||||
}
|
||||
}
|
||||
if (msg.mediumGroupUpdate) {
|
||||
await this.handleMediumGroupUpdate(envelope, msg.mediumGroupUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
const ev = new Event('sent');
|
||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
ev.data = {
|
||||
destination,
|
||||
timestamp: timestamp.toNumber(),
|
||||
device: envelope.sourceDevice,
|
||||
unidentifiedStatus,
|
||||
message,
|
||||
};
|
||||
if (expirationStartTimestamp) {
|
||||
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
||||
}
|
||||
return this.dispatchAndWait(ev);
|
||||
})
|
||||
const message = await this.processDecrypted(envelope, msg);
|
||||
|
||||
const groupId = message.group && message.group.id;
|
||||
const isBlocked = this.isGroupBlocked(groupId);
|
||||
const primaryDevicePubKey = window.storage.get('primaryDevicePubKey');
|
||||
const isMe =
|
||||
envelope.source === textsecure.storage.user.getNumber() ||
|
||||
envelope.source === primaryDevicePubKey;
|
||||
const isLeavingGroup = Boolean(
|
||||
message.group &&
|
||||
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
||||
);
|
||||
|
||||
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
|
||||
window.log.warn(
|
||||
`Message ${this.getEnvelopeId(
|
||||
envelope
|
||||
)} ignored; destined for blocked group`
|
||||
);
|
||||
this.removeFromCache(envelope);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle profileKey and avatar updates
|
||||
if (envelope.source === primaryDevicePubKey) {
|
||||
const { profileKey, profile } = message;
|
||||
const primaryConversation = ConversationController.get(
|
||||
primaryDevicePubKey
|
||||
);
|
||||
if (profile) {
|
||||
this.updateProfile(primaryConversation, profile, profileKey);
|
||||
}
|
||||
}
|
||||
|
||||
const ev = new Event('sent');
|
||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
ev.data = {
|
||||
destination,
|
||||
timestamp: timestamp.toNumber(),
|
||||
device: envelope.sourceDevice,
|
||||
unidentifiedStatus,
|
||||
message,
|
||||
};
|
||||
if (expirationStartTimestamp) {
|
||||
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
||||
}
|
||||
this.dispatchAndWait(ev);
|
||||
},
|
||||
async handleLokiAddressMessage(envelope) {
|
||||
window.log.warn('Ignoring a Loki address message');
|
||||
|
@ -1163,96 +1201,121 @@ MessageReceiver.prototype.extend({
|
|||
},
|
||||
|
||||
async handleMediumGroupUpdate(envelope, groupUpdate) {
|
||||
const {
|
||||
groupId,
|
||||
groupSecretKey,
|
||||
senderKey,
|
||||
members,
|
||||
groupName,
|
||||
} = groupUpdate;
|
||||
const { type, groupId } = groupUpdate;
|
||||
|
||||
const convoExists = window.ConversationController.get(groupId, 'group');
|
||||
|
||||
if (convoExists) {
|
||||
// If the group already exists, check that `members` is empty,
|
||||
// and if so, it is sender key message
|
||||
|
||||
// TODO: introduce TYPE into this message instead?
|
||||
if (!members || !members.length) {
|
||||
log.info('[sender key] got a new sender key from:', envelope.source);
|
||||
|
||||
// We probably don't need to await here
|
||||
await window.SenderKeyAPI.saveSenderKeys(
|
||||
groupId,
|
||||
envelope.source,
|
||||
senderKey
|
||||
);
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
return;
|
||||
}
|
||||
|
||||
log.error(`Conversation for groupId ${groupId} already exists`);
|
||||
}
|
||||
|
||||
const convo = await window.ConversationController.getOrCreateAndWait(
|
||||
groupId,
|
||||
'group'
|
||||
);
|
||||
convo.set('is_medium_group', true);
|
||||
convo.set('active_at', Date.now());
|
||||
convo.set('name', groupName);
|
||||
|
||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||
id: groupId,
|
||||
secretKey: groupSecretKey,
|
||||
});
|
||||
|
||||
// Save sender's key
|
||||
await window.SenderKeyAPI.saveSenderKeys(
|
||||
groupId,
|
||||
envelope.source,
|
||||
senderKey
|
||||
);
|
||||
|
||||
// TODO: Check that we are even a part of this group?
|
||||
const ourIdentity = await textsecure.storage.user.getNumber();
|
||||
const senderIdentity = envelope.source;
|
||||
|
||||
const ownSenderKey = await window.SenderKeyAPI.createSenderKeyForGroup(
|
||||
groupId,
|
||||
ourIdentity
|
||||
);
|
||||
|
||||
{
|
||||
// TODO: Send own key to every member
|
||||
|
||||
const otherMembers = _.without(members, ourIdentity);
|
||||
if (
|
||||
type === textsecure.protobuf.MediumGroupUpdate.Type.SENDER_KEY_REQUEST
|
||||
) {
|
||||
log.debug('[sender key] sender key request from:', senderIdentity);
|
||||
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
|
||||
// We reuse the same message type for sender keys
|
||||
const update = new textsecure.protobuf.MediumGroupUpdate();
|
||||
|
||||
const { chainKey, keyIdx } = await window.SenderKeyAPI.getSenderKeys(
|
||||
groupId,
|
||||
ourIdentity
|
||||
);
|
||||
|
||||
update.type = textsecure.protobuf.MediumGroupUpdate.Type.SENDER_KEY;
|
||||
update.groupId = groupId;
|
||||
update.senderKey = ownSenderKey;
|
||||
update.senderKey = new textsecure.protobuf.SenderKey({
|
||||
chainKey: StringView.arrayBufferToHex(chainKey),
|
||||
keyIdx,
|
||||
});
|
||||
|
||||
proto.mediumGroupUpdate = update;
|
||||
|
||||
// TODO: send to our linked devices too?
|
||||
textsecure.messaging.updateMediumGroup([senderIdentity], proto);
|
||||
|
||||
// Don't need to await here
|
||||
this.removeFromCache(envelope);
|
||||
} else if (type === textsecure.protobuf.MediumGroupUpdate.Type.SENDER_KEY) {
|
||||
const { senderKey } = groupUpdate;
|
||||
|
||||
// TODO: Some of the members might not have a session with us, so
|
||||
// we should send a session request
|
||||
log.debug('[sender key] got a new sender key from:', senderIdentity);
|
||||
|
||||
textsecure.messaging.updateMediumGroup(otherMembers, proto);
|
||||
await window.SenderKeyAPI.saveSenderKeys(
|
||||
groupId,
|
||||
senderIdentity,
|
||||
senderKey.chainKey,
|
||||
senderKey.keyIdx
|
||||
);
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
} else if (type === textsecure.protobuf.MediumGroupUpdate.Type.NEW_GROUP) {
|
||||
const {
|
||||
members: membersBinary,
|
||||
groupSecretKey,
|
||||
groupName,
|
||||
senderKey,
|
||||
} = groupUpdate;
|
||||
|
||||
const members = membersBinary.map(pk =>
|
||||
StringView.arrayBufferToHex(pk.toArrayBuffer())
|
||||
);
|
||||
|
||||
const convo = await window.ConversationController.getOrCreateAndWait(
|
||||
groupId,
|
||||
'group'
|
||||
);
|
||||
convo.set('is_medium_group', true);
|
||||
convo.set('active_at', Date.now());
|
||||
convo.set('name', groupName);
|
||||
|
||||
convo.setFriendRequestStatus(
|
||||
window.friends.friendRequestStatusEnum.friends
|
||||
);
|
||||
|
||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||
id: groupId,
|
||||
secretKey: StringView.arrayBufferToHex(groupSecretKey.toArrayBuffer()),
|
||||
});
|
||||
|
||||
// Save sender's key
|
||||
await window.SenderKeyAPI.saveSenderKeys(
|
||||
groupId,
|
||||
envelope.source,
|
||||
senderKey.chainKey,
|
||||
senderKey.keyIdx
|
||||
);
|
||||
|
||||
// TODO: Check that we are even a part of this group?
|
||||
const ownSenderKey = await window.SenderKeyAPI.createSenderKeyForGroup(
|
||||
groupId,
|
||||
ourIdentity
|
||||
);
|
||||
|
||||
{
|
||||
// Send own key to every member
|
||||
const otherMembers = _.without(members, ourIdentity);
|
||||
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
|
||||
// We reuse the same message type for sender keys
|
||||
const update = new textsecure.protobuf.MediumGroupUpdate();
|
||||
update.type = textsecure.protobuf.MediumGroupUpdate.Type.SENDER_KEY;
|
||||
update.groupId = groupId;
|
||||
update.senderKey = new textsecure.protobuf.SenderKey({
|
||||
chainKey: ownSenderKey,
|
||||
keyIdx: 0,
|
||||
});
|
||||
|
||||
proto.mediumGroupUpdate = update;
|
||||
|
||||
textsecure.messaging.updateMediumGroup(otherMembers, proto);
|
||||
}
|
||||
|
||||
// Subscribe to this group
|
||||
this.pollForAdditionalId(groupId);
|
||||
|
||||
// All further messages (maybe rather than 'control' messages) should come to this group's swarm
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
}
|
||||
|
||||
// Subscribe to this group
|
||||
this.pollForAdditionalId(groupId);
|
||||
|
||||
// All further messages (maybe rather than 'control' messages) should come to this group's swarm
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
},
|
||||
async handleDataMessage(envelope, msg) {
|
||||
window.log.info('data message from', this.getEnvelopeId(envelope));
|
||||
|
@ -1308,14 +1371,33 @@ MessageReceiver.prototype.extend({
|
|||
!_.isEmpty(message.body) &&
|
||||
friendRequestStatusNoneOrExpired;
|
||||
|
||||
// Build a 'message' event i.e. a received message event
|
||||
const ev = new Event('message');
|
||||
|
||||
const source = envelope.senderIdentity || senderPubKey;
|
||||
|
||||
const isOwnDevice = async pubkey => {
|
||||
const primaryDevice = window.storage.get('primaryDevicePubKey');
|
||||
const secondaryDevices = await window.libloki.storage.getPairedDevicesFor(
|
||||
primaryDevice
|
||||
);
|
||||
|
||||
const allDevices = [primaryDevice, ...secondaryDevices];
|
||||
return allDevices.includes(pubkey);
|
||||
};
|
||||
|
||||
const ownDevice = await isOwnDevice(source);
|
||||
|
||||
let ev;
|
||||
if (conversation.get('is_medium_group') && ownDevice) {
|
||||
// Data messages for medium groups don't arrive as sync messages. Instead,
|
||||
// linked devices poll for group messages independently, thus they need
|
||||
// to recognise some of those messages at their own.
|
||||
ev = new Event('sent');
|
||||
} else {
|
||||
ev = new Event('message');
|
||||
}
|
||||
|
||||
if (envelope.senderIdentity) {
|
||||
message.group = {
|
||||
id: envelope.source
|
||||
id: envelope.source,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1340,6 +1422,7 @@ MessageReceiver.prototype.extend({
|
|||
contact,
|
||||
preview,
|
||||
groupInvitation,
|
||||
mediumGroupUpdate,
|
||||
}) {
|
||||
return (
|
||||
!flags &&
|
||||
|
@ -1349,7 +1432,8 @@ MessageReceiver.prototype.extend({
|
|||
_.isEmpty(quote) &&
|
||||
_.isEmpty(contact) &&
|
||||
_.isEmpty(preview) &&
|
||||
_.isEmpty(groupInvitation)
|
||||
_.isEmpty(groupInvitation) &&
|
||||
_.isEmpty(mediumGroupUpdate)
|
||||
);
|
||||
},
|
||||
handleLegacyMessage(envelope) {
|
||||
|
|
|
@ -430,7 +430,7 @@ MessageSender.prototype = {
|
|||
let keysFound = false;
|
||||
// If we don't have a session but we already have prekeys to
|
||||
// start communication then we should use them
|
||||
if (!haveSession && !options.isPublic) {
|
||||
if (!haveSession && !options.isPublic && !options.isMediumGroup) {
|
||||
keysFound = await hasKeys(number);
|
||||
}
|
||||
|
||||
|
@ -710,7 +710,7 @@ MessageSender.prototype = {
|
|||
}
|
||||
// We only want to sync across closed groups that we haven't left
|
||||
const sessionGroups = conversations.filter(
|
||||
c => c.isClosedGroup() && !c.get('left') && c.isFriend()
|
||||
c => c.isClosedGroup() && !c.get('left') && c.isFriend() && !c.get('is_medium_group')
|
||||
);
|
||||
if (sessionGroups.length === 0) {
|
||||
window.console.info('No closed group to sync.');
|
||||
|
@ -975,7 +975,12 @@ MessageSender.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
sendGroupProto(providedNumbers, proto, timestamp = Date.now(), options = {}) {
|
||||
async sendGroupProto(
|
||||
providedNumbers,
|
||||
proto,
|
||||
timestamp = Date.now(),
|
||||
options = {}
|
||||
) {
|
||||
// We always assume that only primary device is a member in the group
|
||||
const primaryDeviceKey =
|
||||
window.storage.get('primaryDevicePubKey') ||
|
||||
|
@ -1014,12 +1019,13 @@ MessageSender.prototype = {
|
|||
);
|
||||
});
|
||||
|
||||
return sendPromise.then(result => {
|
||||
// Sync the group message to our other devices
|
||||
const encoded = textsecure.protobuf.DataMessage.encode(proto);
|
||||
this.sendSyncMessage(encoded, timestamp, null, null, [], [], options);
|
||||
return result;
|
||||
});
|
||||
const result = await sendPromise;
|
||||
|
||||
// Sync the group message to our other devices
|
||||
const encoded = textsecure.protobuf.DataMessage.encode(proto);
|
||||
this.sendSyncMessage(encoded, timestamp, null, null, [], [], options);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async getMessageProto(
|
||||
|
@ -1282,6 +1288,16 @@ MessageSender.prototype = {
|
|||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
||||
},
|
||||
|
||||
requestSenderKeys(sender, groupId) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
const update = new textsecure.protobuf.MediumGroupUpdate();
|
||||
update.type = textsecure.protobuf.MediumGroupUpdate.Type.SENDER_KEY_REQUEST;
|
||||
update.groupId = groupId;
|
||||
proto.mediumGroupUpdate = update;
|
||||
|
||||
textsecure.messaging.updateMediumGroup([sender], proto);
|
||||
},
|
||||
|
||||
leaveGroup(groupId, groupNumbers, options) {
|
||||
const proto = new textsecure.protobuf.DataMessage();
|
||||
proto.group = new textsecure.protobuf.GroupContext();
|
||||
|
@ -1391,6 +1407,7 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
|||
this.setGroupName = sender.setGroupName.bind(sender);
|
||||
this.setGroupAvatar = sender.setGroupAvatar.bind(sender);
|
||||
this.requestGroupInfo = sender.requestGroupInfo.bind(sender);
|
||||
this.requestSenderKeys = sender.requestSenderKeys.bind(sender);
|
||||
this.leaveGroup = sender.leaveGroup.bind(sender);
|
||||
this.sendSyncMessage = sender.sendSyncMessage.bind(sender);
|
||||
this.getProfile = sender.getProfile.bind(sender);
|
||||
|
|
|
@ -51,17 +51,26 @@ message MediumGroupContent {
|
|||
optional bytes ephemeralKey = 2;
|
||||
}
|
||||
|
||||
message MediumGroupUpdate {
|
||||
optional string groupName = 1;
|
||||
optional string groupId = 2; // should this be bytes?
|
||||
optional string groupSecretKey = 3;
|
||||
optional string senderKey = 4;
|
||||
repeated string members = 5;
|
||||
message SenderKey {
|
||||
optional string chainKey = 1;
|
||||
optional uint32 keyIdx = 2;
|
||||
}
|
||||
|
||||
message SenderKeyUpdate {
|
||||
optional string groupId = 1;
|
||||
optional string senderKey = 2;
|
||||
message MediumGroupUpdate {
|
||||
|
||||
enum Type {
|
||||
NEW_GROUP = 0; // groupId, groupName, groupSecretKey, members, senderKey
|
||||
GROUP_INFO = 1; // groupId, groupName, members, senderKey
|
||||
SENDER_KEY_REQUEST = 2; // groupId
|
||||
SENDER_KEY = 3; // groupId, SenderKey
|
||||
}
|
||||
|
||||
optional string groupName = 1;
|
||||
optional string groupId = 2; // should this be bytes?
|
||||
optional bytes groupSecretKey = 3;
|
||||
optional SenderKey senderKey = 4;
|
||||
repeated bytes members = 5;
|
||||
optional Type type = 6;
|
||||
}
|
||||
|
||||
message LokiAddressMessage {
|
||||
|
@ -424,4 +433,5 @@ message GroupDetails {
|
|||
optional string color = 7;
|
||||
optional bool blocked = 8;
|
||||
repeated string admins = 9;
|
||||
optional bool is_medium_group = 10;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue