Merge branch 'clearnet' of https://github.com/loki-project/session-desktop into utils-tests

pull/1199/head
Vincent 5 years ago
commit fadbb37022

@ -1621,22 +1621,17 @@
});
if (Whisper.Import.isComplete()) {
// FIXME Audric; Is that needed for us?
// const {
// wrap,
// sendOptions,
// } = ConversationController.prepareForSend(
// textsecure.storage.user.getNumber(),
// { syncMessage: true }
// );
// wrap(
// textsecure.messaging.sendRequestConfigurationSyncMessage(sendOptions)
// ).catch(error => {
// window.log.error(
// 'Import complete, but failed to send sync message',
// error && error.stack ? error.stack : error
// );
// });
const { CONFIGURATION } = textsecure.protobuf.SyncMessage.Request.Type;
const { RequestSyncMessage } = window.libsession.Messages.Outgoing;
const requestConfigurationSyncMessage = new RequestSyncMessage({
timestamp: Date.now(),
reqestType: CONFIGURATION,
});
await libsession
.getMessageQueue()
.sendSyncMessage(requestConfigurationSyncMessage);
// sending of the message is handled in the 'private' case below
}
}

@ -206,12 +206,10 @@
);
});
},
prepareForSend(id, options) {
prepareForSend(id) {
// id is either a group id or an individual user's id
const conversation = this.get(id);
const sendOptions = conversation
? conversation.getSendOptions(options)
: null;
const sendOptions = {};
const wrap = conversation
? conversation.wrapSend.bind(conversation)
: promise => promise;

@ -1205,10 +1205,10 @@
const expireTimer = this.get('expireTimer');
const recipients = this.getRecipients();
let profileKey;
if (this.get('profileSharing')) {
profileKey = storage.get('profileKey');
}
// let profileKey;
// if (this.get('profileSharing')) {
// profileKey = storage.get('profileKey');
// }
this.queueJob(async () => {
const now = Date.now();
@ -1304,19 +1304,21 @@
now,
});
// Special-case the self-send case - we send only a sync message
// FIXME audric add back profileKey
const chatMessage = new libsession.Messages.Outgoing.ChatMessage({
body: messageBody,
timestamp: Date.now(),
attachments: finalAttachments,
expireTimer,
preview,
quote,
});
// Start handle ChatMessages (attachments/quote/preview/body)
// FIXME AUDRIC handle attachments, quote, preview, profileKey
if (this.isMe()) {
const dataMessage = await textsecure.messaging.getMessageProto(
destination,
messageBody,
finalAttachments,
quote,
preview,
now,
expireTimer,
profileKey
);
return message.sendSyncMessageOnly(dataMessage);
await message.markMessageSyncOnly();
// sending is done in the 'private' case below
}
const options = {};
@ -1364,51 +1366,30 @@
.getMessageQueue()
.sendUsingMultiDevice(destinationPubkey, groupInvitMessage);
}
const chatMessage = new libsession.Messages.Outgoing.ChatMessage({
body,
timestamp: Date.now(),
});
// Start handle ChatMessages (attachments/quote/preview/body)
// FIXME AUDRIC handle attachments, quote, preview
if (conversationType === Message.PRIVATE) {
await libsession
return libsession
.getMessageQueue()
.sendUsingMultiDevice(destinationPubkey, chatMessage);
}
// return textsecure.messaging.sendMessageToNumber(
// destination,
// messageBody,
// finalAttachments,
// quote,
// preview,
// now,
// expireTimer,
// profileKey,
// {}
// );
} else if (conversationType === Message.GROUP) {
// return textsecure.messaging.sendMessageToGroup(
// dest,
// numbers,
// messageBody,
// finalAttachments,
// quote,
// preview,
// now,
// expireTimer,
// profileKey,
// {}
// );
// let dest = destination;
// let numbers = groupNumbers;
if (conversationType === Message.GROUP) {
if (this.isMediumGroup()) {
// FIXME audric to implement back
// dest = this.id;
// numbers = [destination];
// options.isMediumGroup = true;
throw new Error('To implement');
const mediumGroupChatMessage = new libsession.Messages.Outgoing.MediumGroupChatMessage(
{
chatMessage,
groupId: destination,
}
);
const members = this.get('members');
await Promise.all(
members.map(async m => {
const memberPubKey = new libsession.Types.PubKey(m);
await libsession
.getMessageQueue()
.sendUsingMultiDevice(memberPubKey, mediumGroupChatMessage);
})
);
} else {
const closedGroupChatMessage = new libsession.Messages.Outgoing.ClosedGroupChatMessage(
{
@ -1531,78 +1512,6 @@
);
},
getSendOptions(options = {}) {
const senderCertificate = storage.get('senderCertificate');
const numberInfo = this.getNumberInfo(options);
return {
senderCertificate,
numberInfo,
};
},
getNumberInfo(options = {}) {
const { syncMessage, disableMeCheck } = options;
if (!this.ourNumber) {
return null;
}
// START: this code has an Expiration date of ~2018/11/21
// We don't want to enable unidentified delivery for send unless it is
// also enabled for our own account.
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
if (
!disableMeCheck &&
me.get('sealedSender') === SEALED_SENDER.DISABLED
) {
return null;
}
// END
if (!this.isPrivate()) {
const infoArray = this.contactCollection.map(conversation =>
conversation.getNumberInfo(options)
);
return Object.assign({}, ...infoArray);
}
const accessKey = this.get('accessKey');
const sealedSender = this.get('sealedSender');
// We never send sync messages as sealed sender
if (syncMessage && this.id === this.ourNumber) {
return null;
}
// If we've never fetched user's profile, we default to what we have
if (sealedSender === SEALED_SENDER.UNKNOWN) {
return {
[this.id]: {
accessKey:
accessKey ||
window.Signal.Crypto.arrayBufferToBase64(
window.Signal.Crypto.getRandomBytes(16)
),
},
};
}
if (sealedSender === SEALED_SENDER.DISABLED) {
return null;
}
return {
[this.id]: {
accessKey:
accessKey && sealedSender === SEALED_SENDER.ENABLED
? accessKey
: window.Signal.Crypto.arrayBufferToBase64(
window.Signal.Crypto.getRandomBytes(16)
),
},
};
},
async updateSwarmNodes(swarmNodes) {
this.set({ swarmNodes });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
@ -1730,28 +1639,17 @@
profileKey = storage.get('profileKey');
}
if (this.isMe()) {
const flags =
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
const dataMessage = await textsecure.messaging.getMessageProto(
this.get('id'),
null,
[],
null,
[],
message.get('sent_at'),
expireTimer,
profileKey,
flags
);
return message.sendSyncMessageOnly(dataMessage);
}
const expireUpdate = {
timestamp: message.get('sent_at'),
expireTimer,
profileKey,
};
if (this.isMe()) {
await message.markMessageSyncOnly();
// sending of the message is handled in the 'private' case below
}
if (this.get('type') === 'private') {
const expirationTimerMessage = new libsession.Messages.Outgoing.ExpirationTimerUpdateMessage(
expireUpdate
@ -1916,26 +1814,32 @@
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;
const { chainKey, keyIdx } = senderKey;
mgUpdate.type = textsecure.protobuf.MediumGroupUpdate.Type.NEW_GROUP;
mgUpdate.groupId = id;
mgUpdate.groupSecretKey = secretKey;
mgUpdate.senderKey = new textsecure.protobuf.SenderKey(senderKey);
mgUpdate.members = members.map(pkHex =>
StringView.hexToArrayBuffer(pkHex)
);
mgUpdate.groupName = name;
mgUpdate.admins = this.get('groupAdmins');
proto.mediumGroupUpdate = mgUpdate;
const createParams = {
timestamp: Date.now(),
groupId: id,
groupSecretKey: secretKey,
members: members.map(pkHex => StringView.hexToArrayBuffer(pkHex)),
groupName: name,
admins: this.get('groupAdmins'),
chainKey,
keyIdx,
};
message.send(
this.wrapSend(textsecure.messaging.updateMediumGroup(members, proto))
const mediumGroupCreateMessage = new libsession.Messages.Outgoing.MediumGroupCreateMessage(
createParams
);
message.trigger('pending');
members.forEach(member => {
const memberPubKey = new libsession.Types.PubKey(member);
libsession
.getMessageQueue()
.sendUsingMultiDevice(memberPubKey, mediumGroupCreateMessage);
});
return;
}
@ -2122,9 +2026,7 @@
this.ourNumber,
{ syncMessage: true }
);
await this.wrapSend(
textsecure.messaging.syncReadMessages(read, sendOptions)
);
await textsecure.messaging.syncReadMessages(read, sendOptions);
// FIXME AUDRIC
// if (storage.get('read-receipt-setting')) {

@ -28,8 +28,8 @@
deleteExternalMessageFiles,
getAbsoluteAttachmentPath,
loadAttachmentData,
loadQuoteData,
loadPreviewData,
// loadQuoteData,
// loadPreviewData,
} = window.Signal.Migrations;
const { bytesFromString } = window.Signal.Crypto;
@ -1005,9 +1005,9 @@
const successfulRecipients = this.get('sent_to') || [];
const currentRecipients = conversation.getRecipients();
const profileKey = conversation.get('profileSharing')
? storage.get('profileKey')
: null;
// const profileKey = conversation.get('profileSharing')
// ? storage.get('profileKey')
// : null;
let recipients = _.intersection(intendedRecipients, currentRecipients);
recipients = _.without(recipients, successfulRecipients);
@ -1023,73 +1023,62 @@
const attachmentsWithData = await Promise.all(
(this.get('attachments') || []).map(loadAttachmentData)
);
const { body, attachments } = Whisper.Message.getLongMessageAttachment({
const { body } = Whisper.Message.getLongMessageAttachment({
body: this.get('body'),
attachments: attachmentsWithData,
now: this.get('sent_at'),
});
const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview'));
// TODO add logic for attachments, quote and preview here
// don't blindly reuse the one from loadQuoteData loadPreviewData and getLongMessageAttachment.
// they have similar data structure to the ones we need
// but the main difference is that they haven't been uploaded
// so no url exists in them
// so passing it to chat message is incorrect
// const quoteWithData = await loadQuoteData(this.get('quote'));
// const previewWithData = await loadPreviewData(this.get('preview'));
const chatMessage = new libsession.Messages.Outgoing.ChatMessage({
body,
timestamp: this.get('sent_at'),
expireTimer: this.get('expireTimer'),
});
// Special-case the self-send case - we send only a sync message
if (recipients.length === 1 && recipients[0] === this.OUR_NUMBER) {
const [number] = recipients;
const dataMessage = await textsecure.messaging.getMessageProto(
number,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
);
return this.sendSyncMessageOnly(dataMessage);
this.trigger('pending');
// FIXME audric add back profileKey
await this.markMessageSyncOnly();
// sending is done in the private case below
}
let promise;
const options = conversation.getSendOptions();
options.messageType = this.get('type');
if (conversation.isPrivate()) {
const [number] = recipients;
promise = textsecure.messaging.sendMessageToNumber(
number,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey,
options
);
} else {
// Because this is a partial group send, we manually construct the request like
// sendMessageToGroup does.
promise = textsecure.messaging.sendMessage(
{
recipients,
body,
timestamp: this.get('sent_at'),
attachments,
quote: quoteWithData,
preview: previewWithData,
needsSync: !this.get('synced'),
expireTimer: this.get('expireTimer'),
profileKey,
group: {
id: this.get('conversationId'),
type: textsecure.protobuf.GroupContext.Type.DELIVER,
},
},
options
);
const recipientPubKey = new libsession.Types.PubKey(number);
this.trigger('pending');
return libsession
.getMessageQueue()
.sendUsingMultiDevice(recipientPubKey, chatMessage);
}
return this.send(conversation.wrapSend(promise));
this.trigger('pending');
// TODO should we handle open groups message here too? and mediumgroups
// Not sure there is the concept of retrySend for those
const closedGroupChatMessage = new libsession.Messages.Outgoing.ClosedGroupChatMessage(
{
chatMessage,
groupId: this.get('conversationId'),
}
);
// Because this is a partial group send, we send the message with the groupId field set, but individually
// to each recipient listed
return Promise.all(
recipients.map(async r => {
const recipientPubKey = new libsession.Types.PubKey(r);
return libsession
.getMessageQueue()
.sendUsingMultiDevice(recipientPubKey, closedGroupChatMessage);
})
);
},
isReplayableError(e) {
return (
@ -1113,50 +1102,55 @@
return null;
}
const profileKey = null;
const attachmentsWithData = await Promise.all(
(this.get('attachments') || []).map(loadAttachmentData)
);
const { body, attachments } = Whisper.Message.getLongMessageAttachment({
const { body } = Whisper.Message.getLongMessageAttachment({
body: this.get('body'),
attachments: attachmentsWithData,
now: this.get('sent_at'),
});
const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview'));
// TODO add logic for attachments, quote and preview here
// don't blindly reuse the one from loadQuoteData loadPreviewData and getLongMessageAttachment.
// they have similar data structure to the ones we need
// but the main difference is that they haven't been uploaded
// so no url exists in them
// so passing it to chat message is incorrect
// const quoteWithData = await loadQuoteData(this.get('quote'));
// const previewWithData = await loadPreviewData(this.get('preview'));
const chatMessage = new libsession.Messages.Outgoing.ChatMessage({
body,
timestamp: this.get('sent_at'),
expireTimer: this.get('expireTimer'),
});
// Special-case the self-send case - we send only a sync message
if (number === this.OUR_NUMBER) {
const dataMessage = await textsecure.messaging.getMessageProto(
number,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
);
return this.sendSyncMessageOnly(dataMessage);
this.trigger('pending');
await this.markMessageSyncOnly();
// sending is done in the private case below
}
const conversation = this.getConversation();
const recipientPubKey = new libsession.Types.PubKey(number);
const { wrap, sendOptions } = ConversationController.prepareForSend(
number
);
const promise = textsecure.messaging.sendMessageToNumber(
number,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey,
sendOptions
);
if (conversation.isPrivate()) {
this.trigger('pending');
return libsession
.getMessageQueue()
.sendUsingMultiDevice(recipientPubKey, chatMessage);
}
return this.send(wrap(promise));
const closedGroupChatMessage = new libsession.Messages.Outgoing.ClosedGroupChatMessage(
{
chatMessage,
groupId: this.get('conversationId'),
}
);
// resend tries to send the message to that specific user only in the context of a closed group
this.trigger('pending');
return libsession
.getMessageQueue()
.sendUsingMultiDevice(recipientPubKey, closedGroupChatMessage);
},
removeOutgoingErrors(number) {
const errors = _.partition(
@ -1316,15 +1310,6 @@
});
this.trigger('sent', this);
// don't send sync message for EndSession messages
if (!this.isEndSession()) {
const c = this.getConversation();
// Don't bother sending sync messages to public chats
// or groups with sender keys
if (c && !c.isPublic() && !c.isMediumGroup()) {
this.sendSyncMessage();
}
}
})
.catch(result => {
this.trigger('done');
@ -1369,7 +1354,6 @@
expirationStartTimestamp,
unidentifiedDeliveries: result.unidentifiedDeliveries,
});
promises.push(this.sendSyncMessage());
} else {
this.saveErrors(result.errors);
}
@ -1408,84 +1392,18 @@
return false;
},
async sendSyncMessageOnly(dataMessage) {
this.set({ dataMessage });
try {
this.set({
// These are the same as a normal send()
sent_to: [this.OUR_NUMBER],
sent: true,
expirationStartTimestamp: Date.now(),
});
const result = await this.sendSyncMessage();
this.set({
// We have to do this afterward, since we didn't have a previous send!
unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null,
// These are unique to a Note to Self message - immediately read/delivered
delivered_to: [this.OUR_NUMBER],
read_by: [this.OUR_NUMBER],
});
} catch (result) {
const errors = (result && result.errors) || [
new Error('Unknown error'),
];
this.set({ errors });
} finally {
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
this.trigger('done');
const errors = this.get('errors');
if (errors) {
this.trigger('send-error', errors);
} else {
this.trigger('sent');
}
}
},
async sendSyncMessage() {
this.syncPromise = this.syncPromise || Promise.resolve();
const next = async () => {
const encodedDataMessage = this.get('dataMessage');
if (this.get('synced') || !encodedDataMessage) {
return Promise.resolve();
}
const dataMessage = textsecure.protobuf.DataMessage.decode(
encodedDataMessage
);
// Sync the group message to our other devices
const sentSyncMessageParams = {
timestamp: this.get('sent_at'),
dataMessage,
destination: this.get('destination'),
expirationStartTimestamp: this.get('expirationStartTimestamp'),
sent_to: this.get('sent_to'),
unidentifiedDeliveries: this.get('unidentifiedDeliveries'),
};
const sentSyncMessage = new libsession.Messages.Outgoing.SentSyncMessage(
sentSyncMessageParams
);
const result = await libsession
.getMessageQueue()
.sendSyncMessage(sentSyncMessage);
this.set({
synced: true,
dataMessage: null,
});
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
return result;
};
this.syncPromise = this.syncPromise.then(next, next);
async markMessageSyncOnly(dataMessage) {
this.set({
// These are the same as a normal send()
dataMessage,
sent_to: [this.OUR_NUMBER],
sent: true,
expirationStartTimestamp: Date.now(),
});
return this.syncPromise;
return window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
},
async saveErrors(providedErrors) {

@ -850,8 +850,15 @@ MessageReceiver.prototype.extend({
groupId
);
textsecure.messaging.requestSenderKeys(senderIdentity, groupId);
const params = {
timestamp: Date.now(),
groupId,
};
const requestKeysMessage = new libsession.Messages.Outgoing.MediumGroupRequestKeysMessage(
params
);
const senderPubKey = new libsession.Types.PubKey(senderIdentity);
libsession.getMessageQueue().send(senderPubKey, requestKeysMessage);
return;
}

@ -1,4 +1,4 @@
/* global textsecure, WebAPI, libsignal, window, OutgoingMessage, libloki, _, libsession */
/* global textsecure, WebAPI, libsignal, window, libloki, _, libsession */
/* eslint-disable more/no-then, no-bitwise */
@ -351,62 +351,6 @@ MessageSender.prototype = {
});
},
async sendMessage(attrs, options) {
const message = new Message(attrs);
const silent = false;
const publicServer =
options.publicSendData && options.publicSendData.serverAPI;
await Promise.all([
this.uploadAttachments(message, publicServer),
this.uploadThumbnails(message, publicServer),
this.uploadLinkPreviews(message, publicServer),
]);
return new Promise((resolve, reject) => {
this.sendMessageProto(
message.timestamp,
message.recipients,
message.toProto(),
res => {
res.dataMessage = message.toArrayBuffer();
if (res.errors.length > 0) {
reject(res);
} else {
resolve(res);
}
},
silent,
options
);
});
},
sendMessageProto(
timestamp,
numbers,
message,
callback,
silent,
options = {}
) {
// Note: Since we're just doing independant tasks,
// using `async` in the `forEach` loop should be fine.
// If however we want to use the results from forEach then
// we would need to convert this to a Promise.all(numbers.map(...))
numbers.forEach(async number => {
const outgoing = new OutgoingMessage(
this.server,
timestamp,
numbers,
message,
silent,
callback,
options
);
this.queueJobForNumber(number, () => outgoing.sendToNumber(number));
});
},
uploadAvatar(attachment) {
// isRaw is true since the data is already encrypted
// and doesn't need to be encrypted again
@ -435,6 +379,7 @@ MessageSender.prototype = {
const syncMessages = await Promise.all(
chunked.map(c => libloki.api.createContactSyncMessage(c))
);
const syncPromises = syncMessages.map(syncMessage =>
libsession.getMessageQueue().sendSyncMessage(syncMessage)
);
@ -515,7 +460,6 @@ MessageSender.prototype = {
readMessages: reads,
}
);
return libsession.getMessageQueue().sendSyncMessage(syncReadMessages);
}
@ -548,232 +492,10 @@ MessageSender.prototype = {
const verifiedSyncMessage = new window.libsession.Messages.Outgoing.VerifiedSyncMessage(
verifiedSyncParams
);
return libsession.getMessageQueue().sendSyncMessage(verifiedSyncMessage);
},
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') ||
textsecure.storage.user.getNumber();
const numbers = providedNumbers.filter(
number => number !== primaryDeviceKey
);
if (numbers.length === 0) {
return Promise.resolve({
successfulNumbers: [],
failoverNumbers: [],
errors: [],
unidentifiedDeliveries: [],
dataMessage: proto.toArrayBuffer(),
});
}
const sendPromise = new Promise((resolve, reject) => {
const silent = true;
const callback = res => {
res.dataMessage = proto.toArrayBuffer();
if (res.errors.length > 0) {
reject(res);
} else {
resolve(res);
}
};
this.sendMessageProto(
timestamp,
numbers,
proto,
callback,
silent,
options
);
});
const result = await sendPromise;
// Sync the group message to our other devices
const sentSyncMessageParams = {
timestamp,
dataMessage: proto,
};
const sentSyncMessage = new libsession.Messages.Outgoing.SentSyncMessage(
sentSyncMessageParams
);
await libsession.getMessageQueue().sendSyncMessage(sentSyncMessage);
return result;
},
async getMessageProto(
number,
body,
attachments,
quote,
preview,
timestamp,
expireTimer,
profileKey,
flags
) {
const attributes = {
recipients: [number],
body,
timestamp,
attachments,
quote,
preview,
expireTimer,
profileKey,
flags,
};
return this.getMessageProtoObj(attributes);
},
async getMessageProtoObj(attributes) {
const message = new Message(attributes);
await Promise.all([
this.uploadAttachments(message),
this.uploadThumbnails(message),
this.uploadLinkPreviews(message),
]);
return message.toArrayBuffer();
},
getOurProfile() {
try {
// Secondary devices have their profile stored
// in their primary device's conversation
const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = window.ConversationController.get(ourNumber);
return conversation.getLokiProfile();
} catch (e) {
window.log.error(`Failed to get our profile: ${e}`);
return null;
}
},
async sendMessageToNumber(
number,
messageText,
attachments,
quote,
preview,
timestamp,
expireTimer,
profileKey,
options
) {
const profile = this.getOurProfile();
const { groupInvitation, sessionRestoration } = options;
return this.sendMessage(
{
recipients: [number],
body: messageText,
timestamp,
attachments,
quote,
preview,
needsSync: true,
expireTimer,
profileKey,
profile,
undefined,
groupInvitation,
sessionRestoration,
},
options
);
},
async sendMessageToGroup(
groupId,
groupNumbers,
messageText,
attachments,
quote,
preview,
timestamp,
expireTimer,
profileKey,
options
) {
// We always assume that only primary device is a member in the group
const primaryDeviceKey =
window.storage.get('primaryDevicePubKey') ||
textsecure.storage.user.getNumber();
let numbers = groupNumbers.filter(number => number !== primaryDeviceKey);
if (options.isPublic) {
numbers = [groupId];
}
const profile = this.getOurProfile();
let group;
// Medium groups don't need this info
if (!options.isMediumGroup) {
group = {
id: groupId,
type: textsecure.protobuf.GroupContext.Type.DELIVER,
};
}
const attrs = {
recipients: numbers,
body: messageText,
timestamp,
attachments,
quote,
preview,
needsSync: true,
expireTimer,
profileKey,
profile,
group,
};
if (numbers.length === 0) {
return {
successfulNumbers: [],
failoverNumbers: [],
errors: [],
unidentifiedDeliveries: [],
dataMessage: await this.getMessageProtoObj(attrs),
};
}
return this.sendMessage(attrs, options);
},
async updateMediumGroup(members, groupUpdateProto) {
// Automatically request session if not found (updates use pairwise sessions)
const autoSession = true;
await this.sendGroupProto(members, groupUpdateProto, Date.now(), {
isPublic: false,
autoSession,
});
return true;
return libsession.getMessageQueue().sendSyncMessage(verifiedSyncMessage);
},
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);
},
makeProxiedRequest(url, options) {
return this.server.makeProxiedRequest(url, options);
},
@ -791,17 +513,11 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
this.sendOpenGroupsSyncMessage = sender.sendOpenGroupsSyncMessage.bind(
sender
);
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
this.sendMessage = sender.sendMessage.bind(sender);
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
this.updateMediumGroup = sender.updateMediumGroup.bind(sender);
this.requestSenderKeys = sender.requestSenderKeys.bind(sender);
this.uploadAvatar = sender.uploadAvatar.bind(sender);
this.syncReadMessages = sender.syncReadMessages.bind(sender);
this.syncVerification = sender.syncVerification.bind(sender);
this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender);
this.getProxiedSize = sender.getProxiedSize.bind(sender);
this.getMessageProto = sender.getMessageProto.bind(sender);
};
textsecure.MessageSender.prototype = {

@ -23,6 +23,7 @@
timestamp: Date.now(),
reqestType: CONFIGURATION,
});
await libsession
.getMessageQueue()
.sendSyncMessage(requestConfigurationSyncMessage);

@ -79,9 +79,7 @@ export class GroupNotification extends React.Component<Props> {
}
if (!contacts || !contacts.length) {
// FIXME audric
return 'FIXME audric';
// throw new Error('Group update kicked is missing contacts');
throw new Error('Group update kicked is missing contacts');
}
const kickedKey =

@ -1,6 +1,9 @@
import { SignalService } from '../protobuf';
import { removeFromCache } from './cache';
import { EnvelopePlus } from './types';
import { MediumGroupResponseKeysMessage } from '../session/messages/outgoing';
import { getMessageQueue } from '../session';
import { PubKey } from '../session/types';
async function handleSenderKeyRequest(
envelope: EnvelopePlus,
@ -14,26 +17,26 @@ async function handleSenderKeyRequest(
log.debug('[sender key] sender key request from:', senderIdentity);
const proto = new SignalService.DataMessage();
// We reuse the same message type for sender keys
const update = new SignalService.MediumGroupUpdate();
const { chainKey, keyIdx } = await SenderKeyAPI.getSenderKeys(
groupId,
ourIdentity
);
update.type = SignalService.MediumGroupUpdate.Type.SENDER_KEY;
update.groupId = groupId;
update.senderKey = new SignalService.SenderKey({
chainKey: StringView.arrayBufferToHex(chainKey),
const chainKeyHex = StringView.arrayBufferToHex(chainKey);
const responseParams = {
timestamp: Date.now(),
groupId,
chainKey: chainKeyHex,
keyIdx,
});
};
proto.mediumGroupUpdate = update;
const keysResponseMessage = new MediumGroupResponseKeysMessage(
responseParams
);
textsecure.messaging.updateMediumGroup([senderIdentity], proto);
const senderPubKey = new PubKey(senderIdentity);
await getMessageQueue().send(senderPubKey, keysResponseMessage);
removeFromCache(envelope);
}
@ -157,7 +160,7 @@ async function handleNewGroup(envelope: EnvelopePlus, groupUpdate: any) {
senderKey.keyIdx
);
const ownSenderKey = await SenderKeyAPI.createSenderKeyForGroup(
const ownSenderKeyHex = await SenderKeyAPI.createSenderKeyForGroup(
groupId,
ourIdentity
);
@ -166,20 +169,24 @@ async function handleNewGroup(envelope: EnvelopePlus, groupUpdate: any) {
// Send own key to every member
const otherMembers = _.without(members, ourIdentity);
const proto = new SignalService.DataMessage();
// We reuse the same message type for sender keys
const update = new SignalService.MediumGroupUpdate();
update.type = SignalService.MediumGroupUpdate.Type.SENDER_KEY;
update.groupId = groupId;
update.senderKey = new SignalService.SenderKey({
chainKey: ownSenderKey,
const responseParams = {
timestamp: Date.now(),
groupId,
chainKey: ownSenderKeyHex,
keyIdx: 0,
};
const keysResponseMessage = new MediumGroupResponseKeysMessage(
responseParams
);
// send our senderKey to every other member
otherMembers.forEach((member: string) => {
const memberPubKey = new PubKey(member);
getMessageQueue()
.sendUsingMultiDevice(memberPubKey, keysResponseMessage)
.ignore();
});
proto.mediumGroupUpdate = update;
textsecure.messaging.updateMediumGroup(otherMembers, proto);
}
// TODO: !!!! This will need to be re-enabled after message polling refactor !!!!!

@ -55,7 +55,7 @@ async function handleGroups(
if (removedMembers.includes(ourNumber)) {
groupUpdate.kicked = 'You';
attributes.isKickedFromGroup = true;
} else {
} else if (removedMembers.length) {
groupUpdate.kicked = removedMembers;
}
} else if (group.type === GROUP_TYPES.QUIT) {

@ -25,8 +25,12 @@ export class ClosedGroupChatMessage extends ClosedGroupMessage {
return this.getDefaultTTL();
}
protected groupContextType(): SignalService.GroupContext.Type {
return SignalService.GroupContext.Type.DELIVER;
protected groupContext(): SignalService.GroupContext {
// use the parent method to fill id correctly
const groupContext = super.groupContext();
groupContext.type = SignalService.GroupContext.Type.DELIVER;
return groupContext;
}
protected dataProto(): SignalService.DataMessage {

@ -13,7 +13,12 @@ export class ClosedGroupLeaveMessage extends ClosedGroupMessage {
});
}
protected groupContextType(): SignalService.GroupContext.Type {
return SignalService.GroupContext.Type.QUIT;
protected groupContext(): SignalService.GroupContext {
// use the parent method to fill id correctly
const groupContext = super.groupContext();
groupContext.type = SignalService.GroupContext.Type.QUIT;
return groupContext;
}
}

@ -23,13 +23,10 @@ export abstract class ClosedGroupMessage extends DataMessage {
return this.getDefaultTTL();
}
protected abstract groupContextType(): SignalService.GroupContext.Type;
protected groupContext(): SignalService.GroupContext {
const id = new Uint8Array(StringUtils.encode(this.groupId.key, 'utf8'));
const type = this.groupContextType();
return new SignalService.GroupContext({ id, type });
return new SignalService.GroupContext({ id });
}
protected dataProto(): SignalService.DataMessage {

@ -13,7 +13,11 @@ export class ClosedGroupRequestInfoMessage extends ClosedGroupMessage {
});
}
protected groupContextType(): SignalService.GroupContext.Type {
return SignalService.GroupContext.Type.REQUEST_INFO;
protected groupContext(): SignalService.GroupContext {
// use the parent method to fill id correctly
const groupContext = super.groupContext();
groupContext.type = SignalService.GroupContext.Type.REQUEST_INFO;
return groupContext;
}
}

@ -52,14 +52,12 @@ export abstract class ClosedGroupUpdateMessage extends ClosedGroupMessage {
this.avatar = params.avatar;
}
protected groupContextType(): SignalService.GroupContext.Type {
return SignalService.GroupContext.Type.UPDATE;
}
protected groupContext(): SignalService.GroupContext {
// use the parent method to fill id and type correctly
// use the parent method to fill id correctly
const groupContext = super.groupContext();
groupContext.type = SignalService.GroupContext.Type.UPDATE;
if (this.name) {
groupContext.name = this.name;
}

@ -3,4 +3,5 @@ export * from './DeviceUnlinkMessage';
export * from './GroupInvitationMessage';
export * from './ChatMessage';
export * from './group';
export * from './mediumgroup';
export * from './ExpirationTimerUpdateMessage';

@ -0,0 +1,30 @@
import { SignalService } from '../../../../../../protobuf';
import { ChatMessage } from '../ChatMessage';
import { PubKey } from '../../../../../types';
import { MediumGroupMessage } from './MediumGroupMessage';
interface MediumGroupChatMessageParams {
identifier?: string;
groupId: string | PubKey;
chatMessage: ChatMessage;
}
export class MediumGroupChatMessage extends MediumGroupMessage {
private readonly chatMessage: ChatMessage;
constructor(params: MediumGroupChatMessageParams) {
super({
timestamp: params.chatMessage.timestamp,
identifier: params.identifier ?? params.chatMessage.identifier,
groupId: params.groupId,
});
this.chatMessage = params.chatMessage;
}
protected dataProto(): SignalService.DataMessage {
const messageProto = this.chatMessage.dataProto();
messageProto.mediumGroupUpdate = super.dataProto().mediumGroupUpdate;
return messageProto;
}
}

@ -0,0 +1,49 @@
import { SignalService } from '../../../../../../protobuf';
import {
MediumGroupResponseKeysMessage,
MediumGroupResponseKeysParams,
} from './MediumGroupResponseKeysMessage';
interface MediumGroupCreateParams extends MediumGroupResponseKeysParams {
groupSecretKey: Uint8Array;
members: Array<Uint8Array>;
admins: Array<string>;
groupName: string;
}
export abstract class MediumGroupCreateMessage extends MediumGroupResponseKeysMessage {
public readonly groupSecretKey: Uint8Array;
public readonly members: Array<Uint8Array>;
public readonly admins: Array<string>;
public readonly groupName: string;
constructor({
timestamp,
identifier,
chainKey,
keyIdx,
groupId,
groupSecretKey,
members,
admins,
groupName,
}: MediumGroupCreateParams) {
super({ timestamp, identifier, groupId, chainKey, keyIdx });
this.groupSecretKey = groupSecretKey;
this.members = members;
this.admins = admins;
this.groupName = groupName;
}
protected mediumGroupContext(): SignalService.MediumGroupUpdate {
const mediumGroupContext = super.mediumGroupContext();
mediumGroupContext.type = SignalService.MediumGroupUpdate.Type.NEW_GROUP;
mediumGroupContext.groupSecretKey = this.groupSecretKey;
mediumGroupContext.members = this.members;
mediumGroupContext.admins = this.admins;
mediumGroupContext.groupName = this.groupName;
return mediumGroupContext;
}
}

@ -0,0 +1,36 @@
import { DataMessage } from '../DataMessage';
import { SignalService } from '../../../../../../protobuf';
import { MessageParams } from '../../../Message';
import { PubKey } from '../../../../../types';
import { StringUtils } from '../../../../../utils';
export interface MediumGroupMessageParams extends MessageParams {
groupId: string | PubKey;
}
export abstract class MediumGroupMessage extends DataMessage {
public readonly groupId: PubKey;
constructor(params: MediumGroupMessageParams) {
super({
timestamp: params.timestamp,
identifier: params.identifier,
});
this.groupId = PubKey.cast(params.groupId);
}
public ttl(): number {
return this.getDefaultTTL();
}
protected mediumGroupContext(): SignalService.MediumGroupUpdate {
return new SignalService.MediumGroupUpdate({ groupId: this.groupId.key });
}
protected dataProto(): SignalService.DataMessage {
const dataMessage = new SignalService.DataMessage();
dataMessage.mediumGroupUpdate = this.mediumGroupContext();
return dataMessage;
}
}

@ -0,0 +1,13 @@
import { SignalService } from '../../../../../../protobuf';
import { MediumGroupMessage } from '.';
export class MediumGroupRequestKeysMessage extends MediumGroupMessage {
protected mediumGroupContext(): SignalService.MediumGroupUpdate {
const mediumGroupContext = super.mediumGroupContext();
mediumGroupContext.type =
SignalService.MediumGroupUpdate.Type.SENDER_KEY_REQUEST;
return mediumGroupContext;
}
}

@ -0,0 +1,37 @@
import { SignalService } from '../../../../../../protobuf';
import { MediumGroupMessage, MediumGroupMessageParams } from '.';
export interface MediumGroupResponseKeysParams
extends MediumGroupMessageParams {
chainKey: string;
keyIdx: number;
}
export class MediumGroupResponseKeysMessage extends MediumGroupMessage {
public readonly chainKey: string;
public readonly keyIdx: number;
constructor({
timestamp,
identifier,
groupId,
chainKey,
keyIdx,
}: MediumGroupResponseKeysParams) {
super({ timestamp, identifier, groupId });
this.chainKey = chainKey;
this.keyIdx = keyIdx;
}
protected mediumGroupContext(): SignalService.MediumGroupUpdate {
const mediumGroupContext = super.mediumGroupContext();
mediumGroupContext.type = SignalService.MediumGroupUpdate.Type.SENDER_KEY;
mediumGroupContext.senderKey = new SignalService.SenderKey({
chainKey: this.chainKey,
keyIdx: this.keyIdx,
});
return mediumGroupContext;
}
}

@ -0,0 +1,5 @@
export * from './MediumGroupMessage';
export * from './MediumGroupRequestKeysMessage';
export * from './MediumGroupResponseKeysMessage';
export * from './MediumGroupCreateMessage';
export * from './MediumGroupChatMessage';
Loading…
Cancel
Save