diff --git a/js/background.js b/js/background.js index d1b054bd1..c3b33e0ae 100644 --- a/js/background.js +++ b/js/background.js @@ -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 } } diff --git a/js/conversation_controller.js b/js/conversation_controller.js index c018f9abc..faa38d844 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -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; diff --git a/js/models/conversations.js b/js/models/conversations.js index 3f71ae135..6e3dbd861 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -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')) { diff --git a/js/models/messages.js b/js/models/messages.js index e66d95280..36787bd7f 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -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) { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 7c0b48aba..e23e917d4 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -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; } diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index ceae67c30..73d8bad7a 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -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 = { diff --git a/libtextsecure/sync_request.js b/libtextsecure/sync_request.js index f17782566..069eda64a 100644 --- a/libtextsecure/sync_request.js +++ b/libtextsecure/sync_request.js @@ -23,6 +23,7 @@ timestamp: Date.now(), reqestType: CONFIGURATION, }); + await libsession .getMessageQueue() .sendSyncMessage(requestConfigurationSyncMessage); diff --git a/ts/components/conversation/GroupNotification.tsx b/ts/components/conversation/GroupNotification.tsx index 6cac19e26..c01d774a2 100644 --- a/ts/components/conversation/GroupNotification.tsx +++ b/ts/components/conversation/GroupNotification.tsx @@ -79,9 +79,7 @@ export class GroupNotification extends React.Component { } 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 = diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index b47ea51da..be2010a56 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -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 !!!!! diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index f6022d00e..c1520c09c 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -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) { diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupChatMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupChatMessage.ts index a0f75f4b0..4bdc144e8 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupChatMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupChatMessage.ts @@ -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 { diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupLeaveMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupLeaveMessage.ts index 29622a13e..1db46ea83 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupLeaveMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupLeaveMessage.ts @@ -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; } } diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupMessage.ts index 91bb9a874..7586cc3ab 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupMessage.ts @@ -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 { diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupRequestInfoMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupRequestInfoMessage.ts index 43a9cad0b..ac8beb3a5 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupRequestInfoMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupRequestInfoMessage.ts @@ -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; } } diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupUpdateMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupUpdateMessage.ts index e416394fc..3c4aa14c0 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupUpdateMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupUpdateMessage.ts @@ -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; } diff --git a/ts/session/messages/outgoing/content/data/index.ts b/ts/session/messages/outgoing/content/data/index.ts index a8022724b..c76911036 100644 --- a/ts/session/messages/outgoing/content/data/index.ts +++ b/ts/session/messages/outgoing/content/data/index.ts @@ -3,4 +3,5 @@ export * from './DeviceUnlinkMessage'; export * from './GroupInvitationMessage'; export * from './ChatMessage'; export * from './group'; +export * from './mediumgroup'; export * from './ExpirationTimerUpdateMessage'; diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupChatMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupChatMessage.ts new file mode 100644 index 000000000..59d557ab7 --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupChatMessage.ts @@ -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; + } +} diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupCreateMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupCreateMessage.ts new file mode 100644 index 000000000..aed1cc1c3 --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupCreateMessage.ts @@ -0,0 +1,49 @@ +import { SignalService } from '../../../../../../protobuf'; +import { + MediumGroupResponseKeysMessage, + MediumGroupResponseKeysParams, +} from './MediumGroupResponseKeysMessage'; + +interface MediumGroupCreateParams extends MediumGroupResponseKeysParams { + groupSecretKey: Uint8Array; + members: Array; + admins: Array; + groupName: string; +} + +export abstract class MediumGroupCreateMessage extends MediumGroupResponseKeysMessage { + public readonly groupSecretKey: Uint8Array; + public readonly members: Array; + public readonly admins: Array; + 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; + } +} diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupMessage.ts new file mode 100644 index 000000000..6f5f7514b --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupMessage.ts @@ -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; + } +} diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupRequestKeysMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupRequestKeysMessage.ts new file mode 100644 index 000000000..5cc6499cc --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupRequestKeysMessage.ts @@ -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; + } +} diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupResponseKeysMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupResponseKeysMessage.ts new file mode 100644 index 000000000..3dbd8d66c --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupResponseKeysMessage.ts @@ -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; + } +} diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/index.ts b/ts/session/messages/outgoing/content/data/mediumgroup/index.ts new file mode 100644 index 000000000..25f87eb5f --- /dev/null +++ b/ts/session/messages/outgoing/content/data/mediumgroup/index.ts @@ -0,0 +1,5 @@ +export * from './MediumGroupMessage'; +export * from './MediumGroupRequestKeysMessage'; +export * from './MediumGroupResponseKeysMessage'; +export * from './MediumGroupCreateMessage'; +export * from './MediumGroupChatMessage';