diff --git a/js/models/conversations.d.ts b/js/models/conversations.d.ts index 88d27f5d3..9f0decf13 100644 --- a/js/models/conversations.d.ts +++ b/js/models/conversations.d.ts @@ -22,6 +22,7 @@ interface ConversationAttributes { is_medium_group?: boolean; type: string; lastMessage?: string; + avatarPointer?: string; } export interface ConversationModel diff --git a/preload.js b/preload.js index eab5aa735..7db7de11c 100644 --- a/preload.js +++ b/preload.js @@ -494,7 +494,7 @@ const { window.BlockedNumberController = BlockedNumberController; window.deleteAccount = async reason => { - const syncedMessageSent = async () => { + const deleteEverything = async () => { window.log.info( 'configuration message sent successfully. Deleting everything' ); @@ -512,14 +512,14 @@ window.deleteAccount = async reason => { await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded( true ); - await syncedMessageSent(); + await deleteEverything(); } catch (error) { window.log.error( 'Something went wrong deleting all data:', error && error.stack ? error.stack : error ); try { - await syncedMessageSent(); + await deleteEverything(); } catch (e) { window.log.error(e); } diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 4ad25d0a9..a63ecccbe 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -219,6 +219,9 @@ message ConfigurationMessage { repeated ClosedGroup closedGroups = 1; repeated string openGroups = 2; + optional string displayName = 3; + optional string profilePicture = 4; + optional bytes profileKey = 5; } message ReceiptMessage { diff --git a/ts/session/constants.ts b/ts/session/constants.ts index 8b0dfae2d..25f873a8c 100644 --- a/ts/session/constants.ts +++ b/ts/session/constants.ts @@ -5,6 +5,7 @@ export const TTL_DEFAULT = { TYPING_MESSAGE: 20 * SECONDS, REGULAR_MESSAGE: 2 * DAYS, ENCRYPTION_PAIR_GROUP: 4 * DAYS, + CONFIGURATION_MESSAGE: 4 * DAYS, }; // User Interface diff --git a/ts/session/messages/outgoing/content/ConfigurationMessage.ts b/ts/session/messages/outgoing/content/ConfigurationMessage.ts index a71f24af9..699b77ece 100644 --- a/ts/session/messages/outgoing/content/ConfigurationMessage.ts +++ b/ts/session/messages/outgoing/content/ConfigurationMessage.ts @@ -11,16 +11,25 @@ import { PubKey } from '../../../types'; interface ConfigurationMessageParams extends MessageParams { activeClosedGroups: Array; activeOpenGroups: Array; + displayName: string; + profilePicture?: string; + profileKey?: Uint8Array; } 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; 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; if (!this.activeClosedGroups) { throw new Error('closed group must be set'); @@ -29,10 +38,22 @@ export class ConfigurationMessage extends ContentMessage { 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'); + } } public ttl(): number { - return Constants.TTL_DEFAULT.TYPING_MESSAGE; + return Constants.TTL_DEFAULT.CONFIGURATION_MESSAGE; } public contentProto(): SignalService.Content { @@ -45,6 +66,9 @@ export class ConfigurationMessage extends ContentMessage { return new SignalService.ConfigurationMessage({ closedGroups: this.mapClosedGroupsObjectToProto(this.activeClosedGroups), openGroups: this.activeOpenGroups, + displayName: this.displayName, + profilePicture: this.profilePicture, + profileKey: this.profileKey, }); } diff --git a/ts/session/utils/Messages.ts b/ts/session/utils/Messages.ts index 97d7787c0..cbc0d19fe 100644 --- a/ts/session/utils/Messages.ts +++ b/ts/session/utils/Messages.ts @@ -69,6 +69,7 @@ export const getCurrentConfigurationMessage = async ( convos: Array ) => { const ourPubKey = (await UserUtils.getOurNumber()).key; + const ourConvo = convos.find(convo => convo.id === ourPubKey); 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< @@ -107,10 +108,27 @@ export const getCurrentConfigurationMessage = async ( const onlyValidClosedGroup = closedGroups.filter(m => m !== null) as Array< ConfigurationMessageClosedGroup >; + + if (!ourConvo) { + window.log.error( + 'Could not find our convo while building a configuration message.' + ); + } + const profileKeyFromStorage = window.storage.get('profileKey'); + const profileKey = profileKeyFromStorage + ? new Uint8Array(profileKeyFromStorage) + : undefined; + + const profilePicture = ourConvo?.get('avatarPointer') || undefined; + const displayName = ourConvo?.getLokiProfile()?.displayName || undefined; + return new ConfigurationMessage({ identifier: uuid(), timestamp: Date.now(), activeOpenGroups: openGroupsIds, activeClosedGroups: onlyValidClosedGroup, + displayName, + profilePicture, + profileKey, }); }; diff --git a/ts/session/utils/syncUtils.ts b/ts/session/utils/syncUtils.ts index dc0a62b2e..15c5723db 100644 --- a/ts/session/utils/syncUtils.ts +++ b/ts/session/utils/syncUtils.ts @@ -42,39 +42,34 @@ export const syncConfigurationIfNeeded = async () => { export const forceSyncConfigurationNowIfNeeded = async ( waitForMessageSent = false -) => { - const allConvos = ConversationController.getInstance().getConversations(); - const configMessage = await getCurrentConfigurationMessage(allConvos); - window.log.info('forceSyncConfigurationNowIfNeeded with', configMessage); +) => + new Promise(resolve => { + const allConvos = ConversationController.getInstance().getConversations(); - const waitForMessageSentEvent = new Promise(resolve => { - const ourResolver = (message: any) => { - if (message.identifier === configMessage.identifier) { - getMessageQueue().events.off('sendSuccess', ourResolver); - getMessageQueue().events.off('sendFail', ourResolver); - resolve(true); + void getCurrentConfigurationMessage(allConvos).then(configMessage => { + window.log.info('forceSyncConfigurationNowIfNeeded with', configMessage); + + try { + // this just adds the message to the sending queue. + // if waitForMessageSent is set, we need to effectively wait until then + // tslint:disable-next-line: no-void-expression + const callback = waitForMessageSent + ? () => { + resolve(true); + } + : undefined; + void getMessageQueue().sendSyncMessage(configMessage, callback as any); + // either we resolve from the callback if we need to wait for it, + // or we don't want to wait, we resolve it here. + if (!waitForMessageSent) { + resolve(true); + } + } catch (e) { + window.log.warn( + 'Caught an error while sending our ConfigurationMessage:', + e + ); + resolve(false); } - }; - getMessageQueue().events.on('sendSuccess', ourResolver); - getMessageQueue().events.on('sendFail', ourResolver); + }); }); - - try { - // this just adds the message to the sending queue. - // if waitForMessageSent is set, we need to effectively wait until then - await Promise.all([ - getMessageQueue().sendSyncMessage(configMessage), - waitForMessageSentEvent, - ]); - } catch (e) { - window.log.warn( - 'Caught an error while sending our ConfigurationMessage:', - e - ); - } - if (!waitForMessageSent) { - return; - } - - return waitForMessageSentEvent; -}; diff --git a/ts/test/session/unit/messages/ConfigurationMessage_test.ts b/ts/test/session/unit/messages/ConfigurationMessage_test.ts index 04555f9c6..6dc406cb4 100644 --- a/ts/test/session/unit/messages/ConfigurationMessage_test.ts +++ b/ts/test/session/unit/messages/ConfigurationMessage_test.ts @@ -7,6 +7,7 @@ import { } from '../../../../session/messages/outgoing/content/ConfigurationMessage'; import { TestUtils } from '../../../test-utils'; +// tslint:disable-next-line: max-func-body-length describe('ConfigurationMessage', () => { it('throw if closed group is not set', () => { const activeClosedGroups = null as any; @@ -14,6 +15,7 @@ describe('ConfigurationMessage', () => { activeClosedGroups, activeOpenGroups: [], timestamp: Date.now(), + displayName: 'displayName', }; expect(() => new ConfigurationMessage(params)).to.throw( 'closed group must be set' @@ -26,12 +28,48 @@ describe('ConfigurationMessage', () => { activeClosedGroups: [], activeOpenGroups, timestamp: Date.now(), + displayName: 'displayName', }; expect(() => new ConfigurationMessage(params)).to.throw( 'open group must be set' ); }); + it('throw if display name is not set', () => { + const params = { + activeClosedGroups: [], + activeOpenGroups: [], + timestamp: Date.now(), + displayName: undefined as any, + }; + expect(() => new ConfigurationMessage(params)).to.throw( + 'displayName must be set' + ); + }); + + it('throw if display name is set but empty', () => { + const params = { + activeClosedGroups: [], + activeOpenGroups: [], + timestamp: Date.now(), + displayName: undefined as any, + }; + expect(() => new ConfigurationMessage(params)).to.throw( + 'displayName must be set' + ); + }); + + it('ttl is 4 days', () => { + const params = { + activeClosedGroups: [], + activeOpenGroups: [], + timestamp: Date.now(), + displayName: 'displayName', + }; + const configMessage = new ConfigurationMessage(params); + expect(configMessage.ttl()).to.be.equal(4 * 24 * 60 * 60 * 1000); + }); + describe('ConfigurationMessageClosedGroup', () => { it('throw if closed group has no encryptionkeypair', () => { const member = TestUtils.generateFakePubKey().key; diff --git a/ts/test/session/unit/receiving/ConfigurationMessage_test.ts b/ts/test/session/unit/receiving/ConfigurationMessage_test.ts index 0589ff1fc..4426856a8 100644 --- a/ts/test/session/unit/receiving/ConfigurationMessage_test.ts +++ b/ts/test/session/unit/receiving/ConfigurationMessage_test.ts @@ -34,7 +34,8 @@ describe('ConfigurationMessage_receiving', () => { activeOpenGroups: [], activeClosedGroups: [], timestamp: Date.now(), - identifier: 'whatever', + identifier: 'identifier', + displayName: 'displayName', }); }); diff --git a/ts/test/session/unit/utils/Messages_test.ts b/ts/test/session/unit/utils/Messages_test.ts index 8ca9837cb..41d93d03f 100644 --- a/ts/test/session/unit/utils/Messages_test.ts +++ b/ts/test/session/unit/utils/Messages_test.ts @@ -215,6 +215,7 @@ describe('Message Utils', () => { timestamp: Date.now(), activeOpenGroups: [], activeClosedGroups: [], + displayName: 'displayName', }); const rawMessage = await MessageUtils.toRawMessage(device, msg); expect(rawMessage.encryption).to.equal(EncryptionType.Fallback);