add contacts to ConfigurationMessage

This commit is contained in:
Audric Ackermann 2021-02-25 12:50:23 +11:00
parent 0a82bf98fa
commit a61f5e6814
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
8 changed files with 195 additions and 6 deletions

View File

@ -23,6 +23,7 @@ interface ConversationAttributes {
type: string;
lastMessage?: string;
avatarPointer?: string;
profileKey?: Uint8Array;
}
export interface ConversationModel

View File

@ -1297,12 +1297,17 @@
(dataMessage.body && dataMessage.body.length) ||
dataMessage.attachments.length
) {
const syncMessage = libsession.Messages.Outgoing.ChatMessage.buildSyncMessage(
dataMessage,
this.getConversation().id,
sentTimestamp
);
await libsession.getMessageQueue().sendSyncMessage(syncMessage);
// try catch not to be kept
try {
const syncMessage = libsession.Messages.Outgoing.ChatMessage.buildSyncMessage(
dataMessage,
this.getConversation().id,
sentTimestamp
);
await libsession.getMessageQueue().sendSyncMessage(syncMessage);
} catch (e) {
window.log.warn(e);
}
}
// - copy all fields from dataMessage and create a new ChatMessage

View File

@ -217,11 +217,19 @@ message ConfigurationMessage {
repeated bytes admins = 5;
}
message Contact {
optional bytes publicKey = 1;
optional string name = 2;
optional string profilePicture = 3;
optional bytes profileKey = 4;
}
repeated ClosedGroup closedGroups = 1;
repeated string openGroups = 2;
optional string displayName = 3;
optional string profilePicture = 4;
optional bytes profileKey = 5;
repeated Contact contacts = 6;
}
message ReceiptMessage {

View File

@ -14,6 +14,7 @@ interface ConfigurationMessageParams extends MessageParams {
displayName: string;
profilePicture?: string;
profileKey?: Uint8Array;
contacts: Array<ConfigurationMessageContact>;
}
export class ConfigurationMessage extends ContentMessage {
@ -22,6 +23,7 @@ export class ConfigurationMessage extends ContentMessage {
public readonly displayName: string;
public readonly profilePicture?: string;
public readonly profileKey?: Uint8Array;
public readonly contacts: Array<ConfigurationMessageContact>;
constructor(params: ConfigurationMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
@ -30,6 +32,7 @@ export class ConfigurationMessage extends ContentMessage {
this.displayName = params.displayName;
this.profilePicture = params.profilePicture;
this.profileKey = params.profileKey;
this.contacts = params.contacts;
if (!this.activeClosedGroups) {
throw new Error('closed group must be set');
@ -50,6 +53,10 @@ export class ConfigurationMessage extends ContentMessage {
if (this.profileKey && !(this.profileKey instanceof Uint8Array)) {
throw new Error('profileKey set but not an Uin8Array');
}
if (!this.contacts) {
throw new Error('contacts must be set');
}
}
public ttl(): number {
@ -69,6 +76,7 @@ export class ConfigurationMessage extends ContentMessage {
displayName: this.displayName,
profilePicture: this.profilePicture,
profileKey: this.profileKey,
contacts: this.mapContactsObjectToProto(this.contacts),
});
}
@ -79,6 +87,64 @@ export class ConfigurationMessage extends ContentMessage {
new ConfigurationMessageClosedGroup(m).toProto()
);
}
private mapContactsObjectToProto(
contacts: Array<ConfigurationMessageContact>
): Array<SignalService.ConfigurationMessage.Contact> {
return (contacts || []).map(m =>
new ConfigurationMessageContact(m).toProto()
);
}
}
export class ConfigurationMessageContact {
public publicKey: string;
public displayName: string;
public profilePictureURL?: string;
public profileKey?: Uint8Array;
public constructor({
publicKey,
displayName,
profilePictureURL,
profileKey,
}: {
publicKey: string;
displayName: string;
profilePictureURL?: string;
profileKey?: Uint8Array;
}) {
this.publicKey = publicKey;
this.displayName = displayName;
this.profilePictureURL = profilePictureURL;
this.profileKey = profileKey;
// will throw if public key is invalid
PubKey.cast(publicKey);
if (this.displayName?.length === 0) {
throw new Error('displayName must be set or undefined');
}
if (
this.profilePictureURL !== undefined &&
this.profilePictureURL?.length === 0
) {
throw new Error('profilePictureURL must either undefined or not empty');
}
if (this.profileKey !== undefined && this.profileKey?.length === 0) {
throw new Error('profileKey must either undefined or not empty');
}
}
public toProto(): SignalService.ConfigurationMessage.Contact {
return new SignalService.ConfigurationMessage.Contact({
publicKey: fromHexToArray(this.publicKey),
name: this.displayName,
profilePicture: this.profilePictureURL,
profileKey: this.profileKey,
});
}
}
export class ConfigurationMessageClosedGroup {

View File

@ -10,6 +10,7 @@ import { ConversationModel } from '../../../js/models/conversations';
import {
ConfigurationMessage,
ConfigurationMessageClosedGroup,
ConfigurationMessageContact,
} from '../messages/outgoing/content/ConfigurationMessage';
import uuid from 'uuid';
import { getLatestClosedGroupEncryptionKeyPair } from '../../../js/modules/data';
@ -70,11 +71,15 @@ export const getCurrentConfigurationMessage = async (
) => {
const ourPubKey = (await UserUtils.getOurNumber()).key;
const ourConvo = convos.find(convo => convo.id === ourPubKey);
// Filter open groups
const openGroupsIds = convos
.filter(c => !!c.get('active_at') && c.isPublic() && !c.get('left'))
.map(c => c.id.substring((c.id as string).lastIndexOf('@') + 1)) as Array<
string
>;
// Filter Closed/Medium groups
const closedGroupModels = convos.filter(
c =>
!!c.get('active_at') &&
@ -109,6 +114,21 @@ export const getCurrentConfigurationMessage = async (
ConfigurationMessageClosedGroup
>;
// Filter contacts
const contactsModels = convos.filter(
c => !!c.get('active_at') && c.isPrivate() && !c.isBlocked()
);
const contacts = contactsModels.map(c => {
const groupPubKey = c.get('id');
return new ConfigurationMessageContact({
publicKey: groupPubKey,
displayName: c.get('name'),
profilePictureURL: c.get('avatarPointer'),
profileKey: c.get('profileKey'),
});
});
if (!ourConvo) {
window.log.error(
'Could not find our convo while building a configuration message.'
@ -130,5 +150,6 @@ export const getCurrentConfigurationMessage = async (
displayName,
profilePicture,
profileKey,
contacts,
});
};

View File

@ -4,6 +4,7 @@ import { ECKeyPair } from '../../../../receiver/keypairs';
import {
ConfigurationMessage,
ConfigurationMessageClosedGroup,
ConfigurationMessageContact,
} from '../../../../session/messages/outgoing/content/ConfigurationMessage';
import { TestUtils } from '../../../test-utils';
@ -16,6 +17,7 @@ describe('ConfigurationMessage', () => {
activeOpenGroups: [],
timestamp: Date.now(),
displayName: 'displayName',
contacts: [],
};
expect(() => new ConfigurationMessage(params)).to.throw(
'closed group must be set'
@ -29,6 +31,7 @@ describe('ConfigurationMessage', () => {
activeOpenGroups,
timestamp: Date.now(),
displayName: 'displayName',
contacts: [],
};
expect(() => new ConfigurationMessage(params)).to.throw(
'open group must be set'
@ -41,6 +44,7 @@ describe('ConfigurationMessage', () => {
activeOpenGroups: [],
timestamp: Date.now(),
displayName: undefined as any,
contacts: [],
};
expect(() => new ConfigurationMessage(params)).to.throw(
'displayName must be set'
@ -53,6 +57,7 @@ describe('ConfigurationMessage', () => {
activeOpenGroups: [],
timestamp: Date.now(),
displayName: undefined as any,
contacts: [],
};
expect(() => new ConfigurationMessage(params)).to.throw(
'displayName must be set'
@ -65,6 +70,7 @@ describe('ConfigurationMessage', () => {
activeOpenGroups: [],
timestamp: Date.now(),
displayName: 'displayName',
contacts: [],
};
const configMessage = new ConfigurationMessage(params);
expect(configMessage.ttl()).to.be.equal(4 * 24 * 60 * 60 * 1000);
@ -175,4 +181,84 @@ describe('ConfigurationMessage', () => {
);
});
});
describe('ConfigurationMessageContact', () => {
it('throws if contacts is not set', () => {
const params = {
activeClosedGroups: [],
activeOpenGroups: [],
timestamp: Date.now(),
displayName: 'displayName',
contacts: undefined as any,
};
expect(() => new ConfigurationMessage(params)).to.throw(
'contacts must be set'
);
});
it('throw if some admins are not members', () => {
const member = TestUtils.generateFakePubKey().key;
const admin = TestUtils.generateFakePubKey().key;
const params = {
publicKey: TestUtils.generateFakePubKey().key,
name: 'groupname',
members: [member],
admins: [admin],
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
};
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
'some admins are not members'
);
});
it('throw if the contact has not a valid pubkey', () => {
const params = {
publicKey: '05',
displayName: 'contactDisplayName',
};
expect(() => new ConfigurationMessageContact(params)).to.throw();
const params2 = {
publicKey: undefined as any,
displayName: 'contactDisplayName',
};
expect(() => new ConfigurationMessageContact(params2)).to.throw();
});
it('throw if the contact has an empty disploy name', () => {
// a display name cannot be empty. It should be undefined rather than empty
const params2 = {
publicKey: TestUtils.generateFakePubKey().key,
displayName: '',
};
expect(() => new ConfigurationMessageContact(params2)).to.throw();
});
it('throw if the contact has a profileAvatar set but empty', () => {
const params = {
publicKey: TestUtils.generateFakePubKey().key,
displayName: 'contactDisplayName',
profilePictureURL: '',
};
expect(() => new ConfigurationMessageContact(params)).to.throw(
'profilePictureURL must either undefined or not empty'
);
});
it('throw if the contact has a profileKey set but empty', () => {
const params = {
publicKey: TestUtils.generateFakePubKey().key,
displayName: 'contactDisplayName',
profileKey: new Uint8Array(),
};
expect(() => new ConfigurationMessageContact(params)).to.throw(
'profileKey must either undefined or not empty'
);
});
});
});

View File

@ -36,6 +36,7 @@ describe('ConfigurationMessage_receiving', () => {
timestamp: Date.now(),
identifier: 'identifier',
displayName: 'displayName',
contacts: [],
});
});

View File

@ -216,6 +216,7 @@ describe('Message Utils', () => {
activeOpenGroups: [],
activeClosedGroups: [],
displayName: 'displayName',
contacts: [],
});
const rawMessage = await MessageUtils.toRawMessage(device, msg);
expect(rawMessage.encryption).to.equal(EncryptionType.Fallback);