From 6c35f3c77327fb7897beda0bccb79cfab4076731 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 12 Jun 2020 06:02:43 +1000 Subject: [PATCH] optimized OpenGroup --- .../outgoing/content/OpenGroupChatMessage.ts | 0 .../content/sync/ContactSyncMessage.ts | 11 ++- .../outgoing/content/sync/GroupSyncMessage.ts | 44 ++--------- ts/session/sending/MessageQueue.ts | 31 ++++---- ts/session/sending/MessageQueueInterface.ts | 2 +- ts/session/types/OpenGroup.ts | 73 ++++++++++++------- ts/session/utils/Messages.ts | 7 +- ts/session/utils/SyncMessageUtils.ts | 36 ++++++--- .../session/messages/OpenGroupMessage_test.ts | 7 +- 9 files changed, 108 insertions(+), 103 deletions(-) create mode 100644 ts/session/messages/outgoing/content/OpenGroupChatMessage.ts diff --git a/ts/session/messages/outgoing/content/OpenGroupChatMessage.ts b/ts/session/messages/outgoing/content/OpenGroupChatMessage.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ts/session/messages/outgoing/content/sync/ContactSyncMessage.ts b/ts/session/messages/outgoing/content/sync/ContactSyncMessage.ts index 9ad91b161..a23dd937c 100644 --- a/ts/session/messages/outgoing/content/sync/ContactSyncMessage.ts +++ b/ts/session/messages/outgoing/content/sync/ContactSyncMessage.ts @@ -9,18 +9,18 @@ import { ChatMessage, DataMessage } from '../data'; interface ContactSyncMessageParams extends MessageParams { // Send to our devices - linkedDevices: Array; + contacts: Array; dataMessage?: DataMessage; } export class ContactSyncMessage extends SyncMessage { - private readonly linkedDevices: Array; + private readonly contacts: Array; private readonly dataMessage?: DataMessage; constructor(params: ContactSyncMessageParams) { super(params); - this.linkedDevices = params.linkedDevices; + this.contacts = params.contacts; this.dataMessage = params.dataMessage; this.syncProto(); @@ -40,8 +40,11 @@ export class ContactSyncMessage extends SyncMessage { // SignalService.SyncMessage.Read const syncMessage = new SignalService.SyncMessage({ - + }); + + + syncMessage.contacts = this.contacts.map(pubkey => ConversationController.get(pubkey.key)); // TODO: Is this a request sync message or a basic sync message? // Set request type diff --git a/ts/session/messages/outgoing/content/sync/GroupSyncMessage.ts b/ts/session/messages/outgoing/content/sync/GroupSyncMessage.ts index 0b90f4b86..8d22d5447 100644 --- a/ts/session/messages/outgoing/content/sync/GroupSyncMessage.ts +++ b/ts/session/messages/outgoing/content/sync/GroupSyncMessage.ts @@ -7,52 +7,18 @@ import * as Data from '../../../../../../js/modules/data'; import { ChatMessage, DataMessage } from '../data'; -interface ContactSyncMessageParams extends MessageParams { +interface GroupSyncMessageParams extends MessageParams { // Send to our devices linkedDevices: Array; dataMessage?: DataMessage; } -export class ContactSyncMessage extends SyncMessage { - constructor(params: ContactSyncMessageParams) { +export class GroupSyncMessage extends SyncMessage { + constructor(params: GroupSyncMessageParams) { super(params); } - protected syncProto() { + protected syncProto(): SignalService.SyncMessage { - // const contacts = new SignalService.SyncMessage.Contacts(); - // contacts. - - // SignalService.SyncMessage.Configuration - // SignalService.SyncMessage.Contacts.create( - - // ); - // SignalService.SyncMessage.Groups - // SignalService.SyncMessage.OpenGroupDetails - // SignalService.SyncMessage.Read - - const conversations = await Data.getAllConversations({ ConversationCollection: Whisper.ConversationCollection }); - const contacts = conversations.filter((conversation: any) => { - return ( - !conversation.isMe() && - conversation.isPrivate() && - !conversation.isSecondaryDevice() && - conversation.isFriend() - ); - }); - - const syncMessage = await libloki.api.createContactSyncProtoMessage(contacts) as SignalService.SyncMessage; - - // TODO: Is this a request sync message or a basic sync message? - // Set request type - const request = new SignalService.SyncMessage.Request(); - request.type = SignalService.SyncMessage.Request.Type.CONTACTS; - syncMessage.request = request; - - return syncMessage; - } - - // protected dataProto() { - // if dataMess - // } + return new SignalService.SyncMessage(); } diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index e48a530da..ee363857a 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -56,18 +56,22 @@ export class MessageQueue implements MessageQueueInterface { ) { let currentDevices = [...devices]; + // Sync to our devices if syncable if (SyncMessageUtils.canSync(message)) { - // Sync to our devices - const syncMessage = SyncMessageUtils.from(message); - const ourDevices = await this.sendSyncMessage(syncMessage); + + const ourDevices = await SyncMessageUtils.getOurPairedDevices(); + await this.sendSyncMessage(message, ourDevices); // Remove our devices from currentDevices - currentDevices = _.xor(currentDevices, ourDevices); + const ourDeviceContacts = ourDevices.map(device => ConversationController.get(device.key)); + currentDevices = _.xor(currentDevices, ourDeviceContacts); } - currentDevices.forEach(async device => { + const promises = currentDevices.map(async device => { await this.queue(device, message); }); + + return Promise.all(promises); } public async sendToGroup(message: OpenGroupMessage | ContentMessage): Promise { @@ -105,20 +109,17 @@ export class MessageQueue implements MessageQueueInterface { } public async sendSyncMessage( - message: ContentMessage - ): Promise { + message: ContentMessage, + sendTo: Array + ) { // Sync with our devices - - - - const ourDevices = await SyncMessageUtils.getSyncContacts(); - ourDevices.forEach(async device => { + const promises = sendTo.map(async device => { const syncMessage = await SyncMessageUtils.from(message, device); - if (syncMessage) { - await this.queue(device, syncMessage); - } + return this.queue(device, syncMessage); }); + + return Promise.all(promises); } public async processPending(device: PubKey) { diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts index c6ffc328e..b4b374569 100644 --- a/ts/session/sending/MessageQueueInterface.ts +++ b/ts/session/sending/MessageQueueInterface.ts @@ -19,5 +19,5 @@ export interface MessageQueueInterface { sendUsingMultiDevice(user: PubKey, message: ContentMessage): void; send(device: PubKey, message: ContentMessage): void; sendToGroup(message: GroupMessageType): void; - sendSyncMessage(message: ContentMessage): void; + sendSyncMessage(message: ContentMessage, sendTo: Array): Promise>; } diff --git a/ts/session/types/OpenGroup.ts b/ts/session/types/OpenGroup.ts index 58df317db..85cf03f11 100644 --- a/ts/session/types/OpenGroup.ts +++ b/ts/session/types/OpenGroup.ts @@ -1,15 +1,17 @@ // This is the Open Group equivalent to the PubKey type. interface OpenGroupParams { - server?: string; - channel?: number; + server: string; + channel: number; conversationId: string; } export class OpenGroup { - private static readonly conversationIdRegex: RegExp = new RegExp('^publicChat:[0-9]*@([\\w-]{2,}.){1,2}[\\w-]{2,}$'); + private static readonly serverRegex = new RegExp('^([\\w-]{2,}.){1,2}[\\w-]{2,}$'); + private static readonly groupIdRegex = new RegExp('^publicChat:[0-9]*@([\\w-]{2,}.){1,2}[\\w-]{2,}$'); public readonly server: string; public readonly channel: number; + public readonly groupId?: string; public readonly conversationId: string; private readonly isValid: boolean; @@ -17,51 +19,66 @@ export class OpenGroup { this.isValid = OpenGroup.validate(params); if (!this.isValid) { - throw Error('an invalid conversationId was provided'); + throw Error('an invalid server or groupId was provided'); } + const strippedServer = params.server.replace('https://', ''); + + this.server = strippedServer; + this.channel = params.channel; this.conversationId = params.conversationId; - this.server = params.server ?? this.getServer(params.conversationId); - this.channel = params.channel ?? this.getChannel(params.conversationId); + this.groupId = OpenGroup.getGroupId(this.server, this.channel); } - public static from(conversationId: string): OpenGroup | undefined { - // Returns a new instance if conversationId is valid - if (OpenGroup.validate({conversationId})) { - return new OpenGroup({conversationId}); + public static from(groupId: string, conversationId: string): OpenGroup | undefined { + // Returns a new instance from a groupId if it's valid + // eg. groupId = 'publicChat:1@chat.getsession.org' + + // Valid groupId? + if (!this.groupIdRegex.test(groupId)) { + return; + } + + const openGroupParams = { + server: this.getServer(groupId), + channel: this.getChannel(groupId), + groupId, + conversationId, + }; + + if (this.validate(openGroupParams)) { + return new OpenGroup(openGroupParams); } - return undefined; + return; } private static validate(openGroup: OpenGroupParams): boolean { - // Validate conversationId - const { server, channel, conversationId } = openGroup; + // Validate that all the values match by rebuilding groupId. + const { server } = openGroup; - if (!this.conversationIdRegex.test(conversationId)) { + // Valid server? + if (!this.serverRegex.test(server)) { return false; } - // Validate channel and server if provided - if (server && channel) { - const contrivedId = `publicChat:${String(channel)}@${server}`; - if (contrivedId !== conversationId) { - return false; - } - } - return true; } - private getServer(conversationId: string): string { - // conversationId is already validated in constructor; no need to re-check - return conversationId.split('@')[1]; + private static getServer(groupId: string): string { + // groupId is already validated in constructor; no need to re-check + return groupId.split('@')[1]; } - private getChannel(conversationId: string): number { - // conversationId is already validated in constructor; no need to re-check - const channelMatch = conversationId.match(/^.*\:([0-9]*)\@.*$/); + private static getChannel(groupId: string): number { + // groupId is already validated in constructor; no need to re-check + const channelMatch = groupId.match(/^.*\:([0-9]*)\@.*$/); return channelMatch ? Number(channelMatch[1]) : 1; } + + private static getGroupId(server: string, channel: number): string { + // server is already validated in constructor; no need to re-check + return `publicChat:${channel}@${server}`; + } } diff --git a/ts/session/utils/Messages.ts b/ts/session/utils/Messages.ts index b9c8b3096..dca637233 100644 --- a/ts/session/utils/Messages.ts +++ b/ts/session/utils/Messages.ts @@ -1,14 +1,15 @@ import { RawMessage } from '../types/RawMessage'; -import { ContentMessage, SyncMessage } from '../messages/outgoing'; +import { ContentMessage, SyncMessage, OpenGroupMessage } from '../messages/outgoing'; import { EncryptionType, PubKey } from '../types'; import { OpenGroup } from '../types/OpenGroup'; export function toRawMessage( device: PubKey | OpenGroup, - message: ContentMessage + message: ContentMessage | OpenGroupMessage ): RawMessage { - const ttl = message.ttl(); const timestamp = message.timestamp; + + const ttl = message.ttl(); const plainTextBuffer = message.plainTextBuffer(); const sendTo = device instanceof PubKey diff --git a/ts/session/utils/SyncMessageUtils.ts b/ts/session/utils/SyncMessageUtils.ts index 4493cabbc..532cc7630 100644 --- a/ts/session/utils/SyncMessageUtils.ts +++ b/ts/session/utils/SyncMessageUtils.ts @@ -10,18 +10,18 @@ import { EncryptionType, PubKey } from '../types'; import { SignalService } from '../../protobuf'; import { SyncMessageType } from '../messages/outgoing/content/sync/SyncMessage'; +import * as _ from 'lodash'; import * as Data from '../../../js/modules/data'; -import { ConversationController, libloki, Whisper, textsecure } from '../../window'; +import { ConversationController, libloki, textsecure, Whisper } from '../../window'; import { OpenGroup } from '../types/OpenGroup'; import { generateFakePubkey } from '../../test/test-utils/testUtils'; -// export function from(message: ContentMessage): SyncMessage | undefined { -// testtttingggg + export async function from( message: ContentMessage, sendTo: PubKey | OpenGroup, syncType: SyncMessageEnum.CONTACTS | SyncMessageEnum.GROUPS = SyncMessageEnum.CONTACTS -): Promise { +): Promise { const { timestamp, identifier } = message; // Detect Sync Message Type @@ -40,12 +40,13 @@ export async function from( const protoSyncMessage = libloki.api.createContactSyncProtoMessage(contact); const contentMessage = new ContactSyncMessage({ + timestamp, + identifier, dataMessage: protoSyncMessage, - linkedDevices: [generateFakePubkey()], - timestamp: Date.now(), + linkedDevices: [], }); - + break; case SyncMessageEnum.GROUPS: @@ -60,11 +61,14 @@ export async function from( return syncMessage; } -export async function canSync(message: ContentMessage, device: any): Promise { - return Boolean(from(message, device)); +export async function canSync(message: ContentMessage): Promise { + // This function should be agnostic to the device; it shouldn't need + // to know about the recipient + // return Boolean(from(message, device)); + return true; } -export async function getSyncContacts(): Promise> { +export async function getSyncContacts(): Promise> { const thisDevice = textsecure.storage.user.getNumber(); const primaryDevice = await Data.getPrimaryDeviceFor(thisDevice); const conversations = await Data.getAllConversations({ ConversationCollection: Whisper.ConversationCollection }); @@ -95,8 +99,16 @@ export async function getSyncContacts(): Promise> { // Filter out our primary key if it was added here .filter(c => c.id !== primaryDevice); - return new Set([ + // Return unique contacts + return _.uniqBy([ ...primaryContacts, ...secondaryContacts, - ]); + ], device => !!device); +} + +export async function getOurPairedDevices(): Promise> { + const ourPubKey = textsecure.storage.user.getNumber(); + const ourDevices = await Data.getPairedDevicesFor(ourPubKey); + + return ourDevices.map(device => new PubKey(device)); } diff --git a/ts/test/session/messages/OpenGroupMessage_test.ts b/ts/test/session/messages/OpenGroupMessage_test.ts index 7223ca394..44fe696e4 100644 --- a/ts/test/session/messages/OpenGroupMessage_test.ts +++ b/ts/test/session/messages/OpenGroupMessage_test.ts @@ -5,6 +5,7 @@ import { OpenGroupMessage, } from '../../../session/messages/outgoing'; import * as MIME from '../../../../ts/types/MIME'; +import { OpenGroup } from '../../../session/types/OpenGroup'; describe('OpenGroupMessage', () => { const group = { @@ -13,10 +14,14 @@ describe('OpenGroupMessage', () => { conversationId: '0', }; + const group = new OpenGroup({ + server: 'server' + }) + it('can create empty message with just a timestamp and group', () => { const message = new OpenGroupMessage({ timestamp: Date.now(), - group, + group., }); expect(message?.timestamp).to.be.approximately(Date.now(), 10); expect(message?.group).to.deep.equal(group);