optimized OpenGroup

pull/1177/head
Vincent 5 years ago
parent a76a0bed00
commit 6c35f3c773

@ -9,18 +9,18 @@ import { ChatMessage, DataMessage } from '../data';
interface ContactSyncMessageParams extends MessageParams { interface ContactSyncMessageParams extends MessageParams {
// Send to our devices // Send to our devices
linkedDevices: Array<PubKey>; contacts: Array<PubKey>;
dataMessage?: DataMessage; dataMessage?: DataMessage;
} }
export class ContactSyncMessage extends SyncMessage { export class ContactSyncMessage extends SyncMessage {
private readonly linkedDevices: Array<PubKey>; private readonly contacts: Array<PubKey>;
private readonly dataMessage?: DataMessage; private readonly dataMessage?: DataMessage;
constructor(params: ContactSyncMessageParams) { constructor(params: ContactSyncMessageParams) {
super(params); super(params);
this.linkedDevices = params.linkedDevices; this.contacts = params.contacts;
this.dataMessage = params.dataMessage; this.dataMessage = params.dataMessage;
this.syncProto(); this.syncProto();
@ -43,6 +43,9 @@ export class ContactSyncMessage extends SyncMessage {
}); });
syncMessage.contacts = this.contacts.map(pubkey => ConversationController.get(pubkey.key));
// TODO: Is this a request sync message or a basic sync message? // TODO: Is this a request sync message or a basic sync message?
// Set request type // Set request type
const request = new SignalService.SyncMessage.Request(); const request = new SignalService.SyncMessage.Request();

@ -7,52 +7,18 @@ import * as Data from '../../../../../../js/modules/data';
import { ChatMessage, DataMessage } from '../data'; import { ChatMessage, DataMessage } from '../data';
interface ContactSyncMessageParams extends MessageParams { interface GroupSyncMessageParams extends MessageParams {
// Send to our devices // Send to our devices
linkedDevices: Array<PubKey>; linkedDevices: Array<PubKey>;
dataMessage?: DataMessage; dataMessage?: DataMessage;
} }
export class ContactSyncMessage extends SyncMessage { export class GroupSyncMessage extends SyncMessage {
constructor(params: ContactSyncMessageParams) { constructor(params: GroupSyncMessageParams) {
super(params); super(params);
} }
protected syncProto() { protected syncProto(): SignalService.SyncMessage {
// const contacts = new SignalService.SyncMessage.Contacts(); return new SignalService.SyncMessage();
// 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
// }
} }

@ -56,18 +56,22 @@ export class MessageQueue implements MessageQueueInterface {
) { ) {
let currentDevices = [...devices]; let currentDevices = [...devices];
// Sync to our devices if syncable
if (SyncMessageUtils.canSync(message)) { if (SyncMessageUtils.canSync(message)) {
// Sync to our devices
const syncMessage = SyncMessageUtils.from(message); const ourDevices = await SyncMessageUtils.getOurPairedDevices();
const ourDevices = await this.sendSyncMessage(syncMessage); await this.sendSyncMessage(message, ourDevices);
// Remove our devices from currentDevices // 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); await this.queue(device, message);
}); });
return Promise.all(promises);
} }
public async sendToGroup(message: OpenGroupMessage | ContentMessage): Promise<boolean> { public async sendToGroup(message: OpenGroupMessage | ContentMessage): Promise<boolean> {
@ -105,20 +109,17 @@ export class MessageQueue implements MessageQueueInterface {
} }
public async sendSyncMessage( public async sendSyncMessage(
message: ContentMessage message: ContentMessage,
): Promise<void> { sendTo: Array<PubKey>
) {
// Sync with our devices // Sync with our devices
const promises = sendTo.map(async device => {
const ourDevices = await SyncMessageUtils.getSyncContacts();
ourDevices.forEach(async device => {
const syncMessage = await SyncMessageUtils.from(message, device); const syncMessage = await SyncMessageUtils.from(message, device);
if (syncMessage) { return this.queue(device, syncMessage);
await this.queue(device, syncMessage);
}
}); });
return Promise.all(promises);
} }
public async processPending(device: PubKey) { public async processPending(device: PubKey) {

@ -19,5 +19,5 @@ export interface MessageQueueInterface {
sendUsingMultiDevice(user: PubKey, message: ContentMessage): void; sendUsingMultiDevice(user: PubKey, message: ContentMessage): void;
send(device: PubKey, message: ContentMessage): void; send(device: PubKey, message: ContentMessage): void;
sendToGroup(message: GroupMessageType): void; sendToGroup(message: GroupMessageType): void;
sendSyncMessage(message: ContentMessage): void; sendSyncMessage(message: ContentMessage, sendTo: Array<PubKey>): Promise<Array<void>>;
} }

@ -1,15 +1,17 @@
// This is the Open Group equivalent to the PubKey type. // This is the Open Group equivalent to the PubKey type.
interface OpenGroupParams { interface OpenGroupParams {
server?: string; server: string;
channel?: number; channel: number;
conversationId: string; conversationId: string;
} }
export class OpenGroup { 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 server: string;
public readonly channel: number; public readonly channel: number;
public readonly groupId?: string;
public readonly conversationId: string; public readonly conversationId: string;
private readonly isValid: boolean; private readonly isValid: boolean;
@ -17,51 +19,66 @@ export class OpenGroup {
this.isValid = OpenGroup.validate(params); this.isValid = OpenGroup.validate(params);
if (!this.isValid) { 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.conversationId = params.conversationId;
this.server = params.server ?? this.getServer(params.conversationId); this.groupId = OpenGroup.getGroupId(this.server, this.channel);
this.channel = params.channel ?? this.getChannel(params.conversationId);
} }
public static from(conversationId: string): OpenGroup | undefined { public static from(groupId: string, conversationId: string): OpenGroup | undefined {
// Returns a new instance if conversationId is valid // Returns a new instance from a groupId if it's valid
if (OpenGroup.validate({conversationId})) { // eg. groupId = 'publicChat:1@chat.getsession.org'
return new OpenGroup({conversationId});
}
return undefined; // Valid groupId?
if (!this.groupIdRegex.test(groupId)) {
return;
} }
private static validate(openGroup: OpenGroupParams): boolean { const openGroupParams = {
// Validate conversationId server: this.getServer(groupId),
const { server, channel, conversationId } = openGroup; channel: this.getChannel(groupId),
groupId,
conversationId,
};
if (!this.conversationIdRegex.test(conversationId)) { if (this.validate(openGroupParams)) {
return false; return new OpenGroup(openGroupParams);
} }
// Validate channel and server if provided return;
if (server && channel) {
const contrivedId = `publicChat:${String(channel)}@${server}`;
if (contrivedId !== conversationId) {
return false;
} }
private static validate(openGroup: OpenGroupParams): boolean {
// Validate that all the values match by rebuilding groupId.
const { server } = openGroup;
// Valid server?
if (!this.serverRegex.test(server)) {
return false;
} }
return true; return true;
} }
private getServer(conversationId: string): string { private static getServer(groupId: string): string {
// conversationId is already validated in constructor; no need to re-check // groupId is already validated in constructor; no need to re-check
return conversationId.split('@')[1]; return groupId.split('@')[1];
} }
private getChannel(conversationId: string): number { private static getChannel(groupId: string): number {
// conversationId is already validated in constructor; no need to re-check // groupId is already validated in constructor; no need to re-check
const channelMatch = conversationId.match(/^.*\:([0-9]*)\@.*$/); const channelMatch = groupId.match(/^.*\:([0-9]*)\@.*$/);
return channelMatch ? Number(channelMatch[1]) : 1; 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}`;
}
} }

@ -1,14 +1,15 @@
import { RawMessage } from '../types/RawMessage'; import { RawMessage } from '../types/RawMessage';
import { ContentMessage, SyncMessage } from '../messages/outgoing'; import { ContentMessage, SyncMessage, OpenGroupMessage } from '../messages/outgoing';
import { EncryptionType, PubKey } from '../types'; import { EncryptionType, PubKey } from '../types';
import { OpenGroup } from '../types/OpenGroup'; import { OpenGroup } from '../types/OpenGroup';
export function toRawMessage( export function toRawMessage(
device: PubKey | OpenGroup, device: PubKey | OpenGroup,
message: ContentMessage message: ContentMessage | OpenGroupMessage
): RawMessage { ): RawMessage {
const ttl = message.ttl();
const timestamp = message.timestamp; const timestamp = message.timestamp;
const ttl = message.ttl();
const plainTextBuffer = message.plainTextBuffer(); const plainTextBuffer = message.plainTextBuffer();
const sendTo = device instanceof PubKey const sendTo = device instanceof PubKey

@ -10,18 +10,18 @@ import { EncryptionType, PubKey } from '../types';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../protobuf';
import { SyncMessageType } from '../messages/outgoing/content/sync/SyncMessage'; import { SyncMessageType } from '../messages/outgoing/content/sync/SyncMessage';
import * as _ from 'lodash';
import * as Data from '../../../js/modules/data'; 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 { OpenGroup } from '../types/OpenGroup';
import { generateFakePubkey } from '../../test/test-utils/testUtils'; import { generateFakePubkey } from '../../test/test-utils/testUtils';
// export function from(message: ContentMessage): SyncMessage | undefined {
// testtttingggg
export async function from( export async function from(
message: ContentMessage, message: ContentMessage,
sendTo: PubKey | OpenGroup, sendTo: PubKey | OpenGroup,
syncType: SyncMessageEnum.CONTACTS | SyncMessageEnum.GROUPS = SyncMessageEnum.CONTACTS syncType: SyncMessageEnum.CONTACTS | SyncMessageEnum.GROUPS = SyncMessageEnum.CONTACTS
): Promise<SyncMessageType> { ): Promise<ContactSyncMessage> {
const { timestamp, identifier } = message; const { timestamp, identifier } = message;
// Detect Sync Message Type // Detect Sync Message Type
@ -40,12 +40,13 @@ export async function from(
const protoSyncMessage = libloki.api.createContactSyncProtoMessage(contact); const protoSyncMessage = libloki.api.createContactSyncProtoMessage(contact);
const contentMessage = new ContactSyncMessage({ const contentMessage = new ContactSyncMessage({
timestamp,
identifier,
dataMessage: protoSyncMessage, dataMessage: protoSyncMessage,
linkedDevices: [generateFakePubkey()], linkedDevices: [],
timestamp: Date.now(),
}); });
break;
case SyncMessageEnum.GROUPS: case SyncMessageEnum.GROUPS:
@ -60,11 +61,14 @@ export async function from(
return syncMessage; return syncMessage;
} }
export async function canSync(message: ContentMessage, device: any): Promise<boolean> { export async function canSync(message: ContentMessage): Promise<boolean> {
return Boolean(from(message, device)); // 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<Set<any>> { export async function getSyncContacts(): Promise<Array<any>> {
const thisDevice = textsecure.storage.user.getNumber(); const thisDevice = textsecure.storage.user.getNumber();
const primaryDevice = await Data.getPrimaryDeviceFor(thisDevice); const primaryDevice = await Data.getPrimaryDeviceFor(thisDevice);
const conversations = await Data.getAllConversations({ ConversationCollection: Whisper.ConversationCollection }); const conversations = await Data.getAllConversations({ ConversationCollection: Whisper.ConversationCollection });
@ -95,8 +99,16 @@ export async function getSyncContacts(): Promise<Set<any>> {
// Filter out our primary key if it was added here // Filter out our primary key if it was added here
.filter(c => c.id !== primaryDevice); .filter(c => c.id !== primaryDevice);
return new Set([ // Return unique contacts
return _.uniqBy([
...primaryContacts, ...primaryContacts,
...secondaryContacts, ...secondaryContacts,
]); ], device => !!device);
}
export async function getOurPairedDevices(): Promise<Array<PubKey>> {
const ourPubKey = textsecure.storage.user.getNumber();
const ourDevices = await Data.getPairedDevicesFor(ourPubKey);
return ourDevices.map(device => new PubKey(device));
} }

@ -5,6 +5,7 @@ import {
OpenGroupMessage, OpenGroupMessage,
} from '../../../session/messages/outgoing'; } from '../../../session/messages/outgoing';
import * as MIME from '../../../../ts/types/MIME'; import * as MIME from '../../../../ts/types/MIME';
import { OpenGroup } from '../../../session/types/OpenGroup';
describe('OpenGroupMessage', () => { describe('OpenGroupMessage', () => {
const group = { const group = {
@ -13,10 +14,14 @@ describe('OpenGroupMessage', () => {
conversationId: '0', conversationId: '0',
}; };
const group = new OpenGroup({
server: 'server'
})
it('can create empty message with just a timestamp and group', () => { it('can create empty message with just a timestamp and group', () => {
const message = new OpenGroupMessage({ const message = new OpenGroupMessage({
timestamp: Date.now(), timestamp: Date.now(),
group, group.,
}); });
expect(message?.timestamp).to.be.approximately(Date.now(), 10); expect(message?.timestamp).to.be.approximately(Date.now(), 10);
expect(message?.group).to.deep.equal(group); expect(message?.group).to.deep.equal(group);

Loading…
Cancel
Save