groups-test

pull/1177/head
Vincent 5 years ago
commit 2be1c2fd94

@ -1,11 +1,6 @@
import { Message, MessageParams } from './Message';
import { AttachmentPointer, Preview, Quote } from './content';
interface OpenGroup {
server: string;
channel: number;
conversationId: string;
}
import { OpenGroup } from '../../types/OpenGroup';
interface OpenGroupMessageParams extends MessageParams {
group: OpenGroup;
@ -20,7 +15,7 @@ export class OpenGroupMessage extends Message {
public readonly body?: string;
public readonly attachments: Array<AttachmentPointer>;
public readonly quote?: Quote;
public readonly preview: Array<Preview>;
public readonly preview?: Array<Preview>;
constructor({
timestamp,

@ -1,5 +1,18 @@
import { ContentMessage } from '../ContentMessage';
import { SignalService } from '../../../../../protobuf';
// import { ContactSyncMessage } from '.';
// Matches SyncMessage definition in SignalService protobuf
export enum SyncMessageEnum {
UNKNONWN = 0,
CONTACTS = 1,
GROUPS = 2,
BLOCKED = 3,
CONFIGURATION = 4,
}
// TODO: Declare all sync message types
// export type SyncMessageType = ContactSyncMessage | GroupSyncMessage
export abstract class SyncMessage extends ContentMessage {
public ttl(): number {
@ -7,7 +20,10 @@ export abstract class SyncMessage extends ContentMessage {
}
protected contentProto(): SignalService.Content {
const dataMessage = new SignalService.DataMessage({});
return new SignalService.Content({
dataMessage,
syncMessage: this.syncProto(),
});
}

@ -1,6 +1,5 @@
import * as _ from 'lodash';
import { getPairedDevicesFor } from '../../../js/modules/data';
import { ConversationController } from '../../window';
import { EventEmitter } from 'events';
import {
@ -14,11 +13,16 @@ import {
SessionRequestMessage,
} from '../messages/outgoing';
import { PendingMessageCache } from './PendingMessageCache';
import { JobQueue, SyncMessageUtils, TypedEventEmitter } from '../utils';
import {
JobQueue,
SyncMessageUtils,
TypedEventEmitter,
GroupUtils,
} from '../utils';
import { PubKey } from '../types';
import { MessageSender } from '.';
import { SessionProtocol } from '../protocols';
import * as UserUtils from '../../util/user';
import * as UserUtil from '../../util/user';
export class MessageQueue implements MessageQueueInterface {
public readonly events: TypedEventEmitter<MessageQueueInterfaceEvents>;
@ -50,7 +54,7 @@ export class MessageQueue implements MessageQueueInterface {
// Sync to our devices if syncable
if (SyncMessageUtils.canSync(message)) {
const currentDevice = await UserUtils.getCurrentDevicePubKey();
const currentDevice = await UserUtil.getCurrentDevicePubKey();
if (currentDevice) {
const otherDevices = await getPairedDevicesFor(currentDevice);
@ -60,11 +64,7 @@ export class MessageQueue implements MessageQueueInterface {
);
await this.sendSyncMessage(message, ourDevices);
// Remove our devices from currentDevices
const ourDeviceContacts = ourDevices.map(device =>
ConversationController.get(device.key)
);
currentDevices = _.xor(currentDevices, ourDeviceContacts);
currentDevices = _.xor(currentDevices, ourDevices);
}
}
@ -78,22 +78,23 @@ export class MessageQueue implements MessageQueueInterface {
public async sendToGroup(
message: OpenGroupMessage | ContentMessage
): Promise<boolean> {
// Ensure message suits its respective type
if (
!(message instanceof OpenGroupMessage) &&
!(message instanceof ClosedGroupMessage)
) {
console.log('[vince] NOT INSTANCEOF');
console.log('instance of message:', message.constructor.name);
return false;
}
// Closed groups
if (message instanceof ClosedGroupMessage) {
// Get devices in closed group
const conversation = ConversationController.get(message.groupId);
const recipientsModels = conversation.contactCollection.models;
const recipients: Array<PubKey> = recipientsModels.map(
(recipient: any) => new PubKey(recipient.id)
const recipients: Array<PubKey> = await GroupUtils.getGroupMembers(
message.groupId
);
await this.sendMessageToDevices(recipients, message);
return true;
@ -108,11 +109,16 @@ export class MessageQueue implements MessageQueueInterface {
this.events.emit('success', message);
} catch (e) {
this.events.emit('fail', message, e);
console.log('[vince] EVENT FAILED', message);
return false;
}
return true;
}
console.log('[vince] OTHERWISE FAIELD');
return false;
}

@ -93,7 +93,7 @@ export class PendingMessageCache {
await this.saveToDB();
}
public async loadFromDB() {
private async loadFromDB() {
const messages = await this.getFromStorage();
this.cache = messages;
}

@ -0,0 +1,82 @@
// This is the Open Group equivalent to the PubKey type.
interface OpenGroupParams {
server: string;
channel: number;
conversationId: string;
}
export class OpenGroup {
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;
constructor(params: OpenGroupParams) {
const strippedServer = params.server.replace('https://', '');
this.server = strippedServer;
// Validate server format
const isValid = OpenGroup.serverRegex.test(this.server);
if (!isValid) {
throw Error('an invalid server or groupId was provided');
}
this.channel = params.channel;
this.conversationId = params.conversationId;
this.groupId = OpenGroup.getGroupId(this.server, this.channel);
}
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'
const server = this.getServer(groupId);
const channel = this.getChannel(groupId);
// Was groupId successfully utilized?
if (!server || !channel) {
return;
}
const openGroupParams = {
server,
channel,
groupId,
conversationId,
} as OpenGroupParams;
if (this.serverRegex.test(server)) {
return new OpenGroup(openGroupParams);
}
return;
}
private static getServer(groupId: string): string | undefined {
const isValid = this.groupIdRegex.test(groupId);
return isValid ? groupId.split('@')[1] : undefined;
}
private static getChannel(groupId: string): number | undefined {
const isValid = this.groupIdRegex.test(groupId);
const channelMatch = groupId.match(/^.*\:([0-9]*)\@.*$/);
return channelMatch && isValid ? Number(channelMatch[1]) : undefined;
}
private static getGroupId(server: string, channel: number): string {
// server is already validated in constructor; no need to re-check
return `publicChat:${channel}@${server}`;
}
}

@ -26,4 +26,8 @@ export class PubKey {
return false;
}
public static isEqual(first: PubKey, second: PubKey) {
return first.key === second.key;
}
}

@ -0,0 +1,18 @@
import { getAllConversations } from '../../../js/modules/data';
import { Whisper } from '../../window';
import { PubKey } from '../types';
export async function getGroupMembers(groupId: string): Promise<Array<PubKey>> {
const conversations = await getAllConversations({
ConversationCollection: Whisper.ConversationCollection,
});
const groupConversation = conversations.find(c => c.id === groupId);
const groupMembers = groupConversation.attributes.members;
if (!groupMembers) {
return [];
}
return groupMembers.map((member: string) => new PubKey(member));
}

@ -1,21 +1,29 @@
import { RawMessage } from '../types/RawMessage';
import { ContentMessage } 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,
message: ContentMessage
device: PubKey | OpenGroup,
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 ? device.key : device.conversationId;
// tslint:disable-next-line: no-unnecessary-local-variable
const rawMessage: RawMessage = {
identifier: message.identifier,
plainTextBuffer,
timestamp,
device: device.key,
device: sendTo,
ttl,
encryption: EncryptionType.Signal,
};

@ -1,5 +1,5 @@
import * as _ from 'lodash';
import * as UserUtils from '../../util/user';
import * as UserUtil from '../../util/user';
import {
getAllConversations,
getPrimaryDeviceFor,
@ -24,7 +24,7 @@ export async function canSync(message: ContentMessage): Promise<boolean> {
}
export async function getSyncContacts(): Promise<Array<any> | undefined> {
const thisDevice = await UserUtils.getCurrentDevicePubKey();
const thisDevice = await UserUtil.getCurrentDevicePubKey();
if (!thisDevice) {
return [];

@ -1,7 +1,8 @@
import * as MessageUtils from './Messages';
import * as GroupUtils from './Groups';
import * as SyncMessageUtils from './SyncMessageUtils';
export * from './TypedEmitter';
export * from './JobQueue';
export { MessageUtils, SyncMessageUtils };
export { MessageUtils, SyncMessageUtils, GroupUtils };

@ -5,13 +5,14 @@ import {
OpenGroupMessage,
} from '../../../session/messages/outgoing';
import * as MIME from '../../../../ts/types/MIME';
import { OpenGroup } from '../../../session/types/OpenGroup';
describe('OpenGroupMessage', () => {
const group = {
server: 'server',
const group = new OpenGroup({
server: 'chat.example.server',
channel: 1,
conversationId: '0',
};
});
it('can create empty message with just a timestamp and group', () => {
const message = new OpenGroupMessage({

@ -1,9 +1,21 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import * as _ from 'lodash';
import { MessageUtils } from '../../../session/utils';
import { TestUtils } from '../../../test/test-utils';
import { PendingMessageCache, MessageQueue } from '../../../session/sending/MessageQueue';
import { generateFakePubkey, generateChatMessage } from '../../test-utils/testUtils';
import { GroupUtils, MessageUtils } from '../../../session/utils';
import { TestUtils, Stubs } from '../../../test/test-utils';
import { MessageQueue } from '../../../session/sending/MessageQueue';
import {
generateChatMessage,
generateFakePubkey,
generateMemberList,
generateOpenGroupMessage,
} from '../../test-utils/testUtils';
import { getGroupMembers } from '../../../session/utils/Groups';
import { OpenGroupMessage } from '../../../session/messages/outgoing';
import { RawMessage } from '../../../session/types';
import { UserUtil } from '../../../util';
import { MessageSender } from '../../../session/sending';
import { sendToOpenGroup } from '../../../session/sending/MessageSender';
// Equivalent to Data.StorageItem
interface StorageItem {
@ -11,13 +23,21 @@ interface StorageItem {
value: any;
}
describe('Message Queue', () => {
describe('MessageQueue', () => {
const sandbox = sinon.createSandbox();
const ourNumber = generateFakePubkey().key;
// Initialize new stubbed cache
let data: StorageItem;
let messageQueueStub: MessageQueue;
let sendStub: sinon.SinonStub<[RawMessage, (number | undefined)?]>;
let sendToOpenGroupStub: sinon.SinonStub<[OpenGroupMessage]>;
beforeEach(async () => {
// Stub out methods which touch the database
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
// PendingMessageCache stubs
const storageID = 'pendingMessages';
data = {
id: storageID,
@ -36,20 +56,41 @@ describe('Message Queue', () => {
}
});
TestUtils.stubData('getPairedDevicesFor').callsFake(async () => {
return generateMemberList(2);
});
TestUtils.stubWindow('libsignal', {
SignalProtocolAddress: sandbox.stub(),
SessionCipher: Stubs.SessionCipherStub,
} as any);
// Other stubs
sendStub = sandbox.stub(MessageSender, 'send').resolves(undefined);
sendToOpenGroupStub = sandbox.stub(MessageSender, 'sendToOpenGroup').resolves(true);
sandbox.stub(GroupUtils, 'getGroupMembers').callsFake(
async () =>
new Promise(r => {
r(generateMemberList(10));
})
);
messageQueueStub = new MessageQueue();
});
afterEach(() => {
TestUtils.restoreStubs();
sandbox.restore();
});
it('can send to many devices', async () => {
const devices = Array.from({length: 40}, generateFakePubkey);
const devices = generateMemberList(10);
const message = generateChatMessage();
await messageQueueStub.sendMessageToDevices(devices, message);
// Failure will make an error
// Failure will make an error; check messageQueueStub.events
});
it('can send using multidevice', async () => {
@ -57,15 +98,64 @@ describe('Message Queue', () => {
const message = generateChatMessage();
await messageQueueStub.sendUsingMultiDevice(device, message);
// Failure will make an error; check messageQueueStub.events
});
it('', async () => {
it('can send to open group', async () => {
const message = generateOpenGroupMessage();
const success = await messageQueueStub.sendToGroup(message);
expect(success).to.equal(true, 'sending to group failed');
// Failure will make an error; check messageQueueStub.events
});
it('can send to closed group', async () => {
const message = generateOpenGroupMessage();
const success = await messageQueueStub.sendToGroup(message);
expect(success).to.equal(true, 'sending to group failed');
// Failure will make an error; check messageQueueStub.events
});
it('can send to open group', async () => {
const message = generateOpenGroupMessage();
await messageQueueStub.sendToGroup(message);
// Failure will make an error; check messageQueueStub.events
});
it('wont send wrong message type to group', async () => {
// Regular chat message should return false
const message = generateChatMessage();
const response = await messageQueueStub.sendToGroup(message);
expect(response).to.equal(
false,
'sendToGroup considered an invalid message type as valid'
);
// Failure will make an error; check messageQueueStub.events
});
it("won't process invalid message", async () => {
// process with message undefined
// SHOULD make an error; expect error
// EXAMPLE FROM MESSAGESENDER_TEST
// it('should not retry if an error occurred during encryption', async () => {
// encryptStub.throws(new Error('Failed to encrypt.'));
// const promise = MessageSender.send(rawMessage);
// await expect(promise).is.rejectedWith('Failed to encrypt.');
// expect(lokiMessageAPIStub.sendMessage.callCount).to.equal(0);
// });
});
it('can send sync message', async () => {
});
});

@ -5,8 +5,9 @@ import * as DataShape from '../../../js/modules/data';
import { v4 as uuid } from 'uuid';
import { ImportMock } from 'ts-mock-imports';
import { PubKey } from '../../../ts/session/types';
import { PubKey, PubKey } from '../../../ts/session/types';
import { ChatMessage, OpenGroupMessage } from '../../session/messages/outgoing';
import { OpenGroup } from '../../session/types/OpenGroup';
const sandbox = sinon.createSandbox();
@ -69,11 +70,26 @@ export function generateChatMessage(): ChatMessage {
}
export function generateOpenGroupMessage(): OpenGroupMessage {
const group = new OpenGroup()
const group = new OpenGroup({
server: 'chat.example.server',
channel: 0,
conversationId: '0',
});
return new OpenGroupMessage({
group
timestamp: Date.now(),
group,
attachments: undefined,
preview: undefined,
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
quote: undefined,
});
}
export function generateMemberList(size: number): Array<PubKey> {
const numMembers = Math.floor(size);
return numMembers > 0
? Array.from({ length: numMembers }, generateFakePubkey)
: [];
}

@ -15,3 +15,5 @@ export async function getIdentityKeyPair(): Promise<KeyPair | undefined> {
return item?.value;
}
export async function getOurDevices()

@ -134,9 +134,11 @@ export const deleteAccount = window.deleteAccount;
export const resetDatabase = window.resetDatabase;
export const attemptConnection = window.attemptConnection;
export const dcodeIO = window.dcodeIO;
export const libloki = window.libloki;
export const libsignal = window.libsignal;
export const textsecure = window.textsecure;
export const storage = window.storage;
export const lokiMessageAPI = window.lokiMessageAPI;
export const lokiPublicChatAPI = window.lokiPublicChatAPI;

Loading…
Cancel
Save