// this is not a very good name, but a configuration message is a message sent to our other devices so sync our current public and closed groups import { SignalService } from '../../../../protobuf'; import { MessageParams } from '../Message'; import { ECKeyPair } from '../../../../receiver/keypairs'; import { fromHexToArray } from '../../../utils/String'; import { PubKey } from '../../../types'; import { ContentMessage } from '..'; interface ConfigurationMessageParams extends MessageParams { activeClosedGroups: Array; activeOpenGroups: Array; displayName: string; profilePicture?: string; profileKey?: Uint8Array; contacts: Array; } export class ConfigurationMessage extends ContentMessage { public readonly activeClosedGroups: Array; public readonly activeOpenGroups: Array; public readonly displayName: string; public readonly profilePicture?: string; public readonly profileKey?: Uint8Array; public readonly contacts: Array; constructor(params: ConfigurationMessageParams) { super({ timestamp: params.timestamp, identifier: params.identifier }); this.activeClosedGroups = params.activeClosedGroups; this.activeOpenGroups = params.activeOpenGroups; 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'); } if (!this.activeOpenGroups) { throw new Error('open group must be set'); } if (!this.displayName || !this.displayName?.length) { throw new Error('displayName must be set'); } if (this.profilePicture && typeof this.profilePicture !== 'string') { throw new Error('profilePicture set but not an Uin8Array'); } 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 contentProto(): SignalService.Content { return new SignalService.Content({ configurationMessage: this.configurationProto(), }); } protected configurationProto(): SignalService.ConfigurationMessage { return new SignalService.ConfigurationMessage({ closedGroups: this.mapClosedGroupsObjectToProto(this.activeClosedGroups), openGroups: this.activeOpenGroups, displayName: this.displayName, profilePicture: this.profilePicture, profileKey: this.profileKey, contacts: this.mapContactsObjectToProto(this.contacts), }); } private mapClosedGroupsObjectToProto( closedGroups: Array ): Array { return (closedGroups || []).map(m => m.toProto()); } private mapContactsObjectToProto( contacts: Array ): Array { return (contacts || []).map(m => m.toProto()); } } export class ConfigurationMessageContact { public publicKey: string; public displayName: string; public profilePictureURL?: string; public profileKey?: Uint8Array; public isApproved?: boolean; public isBlocked?: boolean; public constructor({ publicKey, displayName, profilePictureURL, profileKey, isApproved, isBlocked, }: { publicKey: string; displayName: string; profilePictureURL?: string; profileKey?: Uint8Array; isApproved?: boolean; isBlocked?: boolean; }) { this.publicKey = publicKey; this.displayName = displayName; this.profilePictureURL = profilePictureURL; this.profileKey = profileKey; this.isApproved = isApproved; this.isBlocked = isBlocked; // 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, isApproved: this.isApproved, isBlocked: this.isBlocked, }); } } export class ConfigurationMessageClosedGroup { public publicKey: string; public name: string; public encryptionKeyPair: ECKeyPair; public members: Array; public admins: Array; public constructor({ publicKey, name, encryptionKeyPair, members, admins, }: { publicKey: string; name: string; encryptionKeyPair: ECKeyPair; members: Array; admins: Array; }) { this.publicKey = publicKey; this.name = name; this.encryptionKeyPair = encryptionKeyPair; this.members = members; this.admins = admins; // will throw if publik key is invalid PubKey.cast(publicKey); if ( !encryptionKeyPair?.privateKeyData?.byteLength || !encryptionKeyPair?.publicKeyData?.byteLength ) { throw new Error('Encryption key pair looks invalid'); } if (!this.name?.length) { throw new Error('name must be set'); } if (!this.members?.length) { throw new Error('members must be set'); } if (!this.admins?.length) { throw new Error('admins must be set'); } if (this.admins.some(a => !this.members.includes(a))) { throw new Error('some admins are not members'); } } public toProto(): SignalService.ConfigurationMessage.ClosedGroup { return new SignalService.ConfigurationMessage.ClosedGroup({ publicKey: fromHexToArray(this.publicKey), name: this.name, encryptionKeyPair: { publicKey: this.encryptionKeyPair.publicKeyData, privateKey: this.encryptionKeyPair.privateKeyData, }, members: this.members.map(fromHexToArray), admins: this.admins.map(fromHexToArray), }); } }