Skip to content

Commit 3788f5a

Browse files
authored
Merge pull request oxen-io#2488 from Bilb/message-request-include-profile
Profile in messageRequest
2 parents c33e4e0 + 4ed837e commit 3788f5a

File tree

6 files changed

+245
-30
lines changed

6 files changed

+245
-30
lines changed

protos/SignalService.proto

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ message Unsend {
3737

3838
message MessageRequestResponse {
3939
// @required
40-
required bool isApproved = 1;
40+
required bool isApproved = 1;
41+
optional bytes profileKey = 2;
42+
optional DataMessage.LokiProfile profile = 3;
4143
}
4244

4345
message Content {

ts/models/conversation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
926926

927927
const messageRequestResponseParams: MessageRequestResponseParams = {
928928
timestamp,
929-
// lokiProfile: UserUtils.getOurProfile(), // we can't curently include our profile in that response
929+
lokiProfile: UserUtils.getOurProfile(),
930930
};
931931

932932
const messageRequestResponse = new MessageRequestResponse(messageRequestResponseParams);

ts/receiver/contentMessage.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '../interactions/conversations/unsendingInteractions';
2828
import { ConversationTypeEnum } from '../models/conversationAttributes';
2929
import { findCachedBlindedMatchOrLookupOnAllServers } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
30+
import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
3031

3132
export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) {
3233
try {
@@ -605,6 +606,11 @@ async function handleMessageRequestResponse(
605606
messageRequestResponse: SignalService.MessageRequestResponse
606607
) {
607608
const { isApproved } = messageRequestResponse;
609+
if (!isApproved) {
610+
window?.log?.error('handleMessageRequestResponse: isApproved is false -- dropping message.');
611+
await removeFromCache(envelope);
612+
return;
613+
}
608614
if (!messageRequestResponse) {
609615
window?.log?.error('handleMessageRequestResponse: Invalid parameters -- dropping message.');
610616
await removeFromCache(envelope);
@@ -675,6 +681,14 @@ async function handleMessageRequestResponse(
675681
}
676682
}
677683

684+
if (messageRequestResponse.profile && !isEmpty(messageRequestResponse.profile)) {
685+
void appendFetchAvatarAndProfileJob(
686+
conversationToApprove,
687+
messageRequestResponse.profile,
688+
messageRequestResponse.profileKey
689+
);
690+
}
691+
678692
if (!conversationToApprove || conversationToApprove.didApproveMe() === isApproved) {
679693
if (conversationToApprove) {
680694
await conversationToApprove.commit();

ts/session/messages/outgoing/controlMessage/MessageRequestResponse.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import { SignalService } from '../../../../protobuf';
2+
import { LokiProfile } from '../../../../types/Message';
23
import { ContentMessage } from '../ContentMessage';
34
import { MessageParams } from '../Message';
5+
import { buildProfileForOutgoingMessage } from '../visibleMessage/VisibleMessage';
46

57
// tslint:disable-next-line: no-empty-interface
6-
export interface MessageRequestResponseParams extends MessageParams {}
8+
export interface MessageRequestResponseParams extends MessageParams {
9+
lokiProfile?: LokiProfile;
10+
}
711

812
export class MessageRequestResponse extends ContentMessage {
913
// we actually send a response only if it is an accept
1014
// private readonly isApproved: boolean;
15+
private readonly profileKey?: Uint8Array;
16+
private readonly profile?: SignalService.DataMessage.ILokiProfile;
1117

1218
constructor(params: MessageRequestResponseParams) {
1319
super({
1420
timestamp: params.timestamp,
1521
} as MessageRequestResponseParams);
22+
23+
const profile = buildProfileForOutgoingMessage(params);
24+
this.profile = profile.lokiProfile;
25+
this.profileKey = profile.profileKey;
1626
}
1727

1828
public contentProto(): SignalService.Content {
@@ -24,6 +34,8 @@ export class MessageRequestResponse extends ContentMessage {
2434
public messageRequestResponseProto(): SignalService.MessageRequestResponse {
2535
return new SignalService.MessageRequestResponse({
2636
isApproved: true,
37+
profileKey: this.profileKey?.length ? this.profileKey : undefined,
38+
profile: this.profile,
2739
});
2840
}
2941
}

ts/session/messages/outgoing/visibleMessage/VisibleMessage.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ByteBuffer from 'bytebuffer';
2+
import { isEmpty } from 'lodash';
23
import { DataMessage } from '..';
34
import { SignalService } from '../../../../protobuf';
45
import { LokiProfile } from '../../../../types/Message';
@@ -80,8 +81,7 @@ export class VisibleMessage extends DataMessage {
8081
private readonly body?: string;
8182
private readonly quote?: Quote;
8283
private readonly profileKey?: Uint8Array;
83-
private readonly displayName?: string;
84-
private readonly avatarPointer?: string;
84+
private readonly profile?: SignalService.DataMessage.ILokiProfile;
8585
private readonly preview?: Array<PreviewWithAttachmentUrl>;
8686

8787
/// In the case of a sync message, the public key of the person the message was targeted at.
@@ -94,21 +94,12 @@ export class VisibleMessage extends DataMessage {
9494
this.body = params.body;
9595
this.quote = params.quote;
9696
this.expireTimer = params.expireTimer;
97-
if (params.lokiProfile && params.lokiProfile.profileKey) {
98-
if (
99-
params.lokiProfile.profileKey instanceof Uint8Array ||
100-
(params.lokiProfile.profileKey as any) instanceof ByteBuffer
101-
) {
102-
this.profileKey = new Uint8Array(params.lokiProfile.profileKey);
103-
} else {
104-
this.profileKey = new Uint8Array(
105-
ByteBuffer.wrap(params.lokiProfile.profileKey).toArrayBuffer()
106-
);
107-
}
108-
}
10997

110-
this.displayName = params.lokiProfile && params.lokiProfile.displayName;
111-
this.avatarPointer = params.lokiProfile && params.lokiProfile.avatarPointer;
98+
const profile = buildProfileForOutgoingMessage(params);
99+
100+
this.profile = profile.lokiProfile;
101+
this.profileKey = profile.profileKey;
102+
112103
this.preview = params.preview;
113104
this.reaction = params.reaction;
114105
this.syncTarget = params.syncTarget;
@@ -137,18 +128,10 @@ export class VisibleMessage extends DataMessage {
137128
dataMessage.syncTarget = this.syncTarget;
138129
}
139130

140-
if (this.avatarPointer || this.displayName) {
141-
const profile = new SignalService.DataMessage.LokiProfile();
142-
143-
if (this.avatarPointer) {
144-
profile.profilePicture = this.avatarPointer;
145-
}
146-
147-
if (this.displayName) {
148-
profile.displayName = this.displayName;
149-
}
150-
dataMessage.profile = profile;
131+
if (this.profile) {
132+
dataMessage.profile = this.profile;
151133
}
134+
152135
if (this.profileKey && this.profileKey.length) {
153136
dataMessage.profileKey = this.profileKey;
154137
}
@@ -201,3 +184,47 @@ export class VisibleMessage extends DataMessage {
201184
return this.identifier === comparator.identifier && this.timestamp === comparator.timestamp;
202185
}
203186
}
187+
188+
export function buildProfileForOutgoingMessage(params: { lokiProfile?: LokiProfile }) {
189+
let profileKey: Uint8Array | undefined;
190+
if (params.lokiProfile && params.lokiProfile.profileKey) {
191+
if (
192+
params.lokiProfile.profileKey instanceof Uint8Array ||
193+
(params.lokiProfile.profileKey as any) instanceof ByteBuffer
194+
) {
195+
profileKey = new Uint8Array(params.lokiProfile.profileKey);
196+
} else {
197+
profileKey = new Uint8Array(ByteBuffer.wrap(params.lokiProfile.profileKey).toArrayBuffer());
198+
}
199+
}
200+
201+
const displayName = params.lokiProfile?.displayName;
202+
203+
// no need to iclude the avatarPointer if there is no profileKey associated with it.
204+
const avatarPointer =
205+
params.lokiProfile?.avatarPointer &&
206+
!isEmpty(profileKey) &&
207+
params.lokiProfile.avatarPointer &&
208+
!isEmpty(params.lokiProfile.avatarPointer)
209+
? params.lokiProfile.avatarPointer
210+
: undefined;
211+
212+
let lokiProfile: SignalService.DataMessage.ILokiProfile | undefined;
213+
if (avatarPointer || displayName) {
214+
lokiProfile = new SignalService.DataMessage.LokiProfile();
215+
216+
// we always need a profileKey tom decode an avatar pointer
217+
if (avatarPointer && avatarPointer.length && profileKey) {
218+
lokiProfile.profilePicture = avatarPointer;
219+
}
220+
221+
if (displayName) {
222+
lokiProfile.displayName = displayName;
223+
}
224+
}
225+
226+
return {
227+
lokiProfile,
228+
profileKey: lokiProfile?.profilePicture ? profileKey : undefined,
229+
};
230+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { expect } from 'chai';
2+
import { v4 } from 'uuid';
3+
4+
import { SignalService } from '../../../../protobuf';
5+
import { Constants } from '../../../../session';
6+
import { MessageRequestResponse } from '../../../../session/messages/outgoing/controlMessage/MessageRequestResponse';
7+
// tslint:disable: no-unused-expression
8+
9+
// tslint:disable-next-line: max-func-body-length
10+
describe('MessageRequestResponse', () => {
11+
let message: MessageRequestResponse | undefined;
12+
it('correct ttl', () => {
13+
message = new MessageRequestResponse({
14+
timestamp: Date.now(),
15+
});
16+
17+
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TTL_MAX);
18+
});
19+
20+
it('has an identifier', () => {
21+
message = new MessageRequestResponse({
22+
timestamp: Date.now(),
23+
});
24+
25+
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
26+
expect(message.identifier).to.not.equal(undefined, 'identifier cannot be undefined');
27+
});
28+
29+
it('has an identifier matching if given', () => {
30+
const identifier = v4();
31+
message = new MessageRequestResponse({
32+
timestamp: Date.now(),
33+
identifier,
34+
});
35+
36+
expect(message.identifier).to.not.equal(identifier, 'identifier should match');
37+
});
38+
39+
it('isApproved is always true', () => {
40+
message = new MessageRequestResponse({
41+
timestamp: Date.now(),
42+
});
43+
const plainText = message.plainTextBuffer();
44+
const decoded = SignalService.Content.decode(plainText);
45+
expect(decoded.messageRequestResponse)
46+
.to.have.property('isApproved')
47+
.to.be.eq(true, 'isApproved is true');
48+
});
49+
50+
it('can create response without lokiProfile', () => {
51+
message = new MessageRequestResponse({
52+
timestamp: Date.now(),
53+
});
54+
const plainText = message.plainTextBuffer();
55+
const decoded = SignalService.Content.decode(plainText);
56+
expect(decoded.messageRequestResponse)
57+
.to.have.property('profile')
58+
.to.be.eq(null, 'no profile field if no profile given');
59+
});
60+
61+
it('can create response with display name only', () => {
62+
message = new MessageRequestResponse({
63+
timestamp: Date.now(),
64+
lokiProfile: { displayName: 'Jane', profileKey: null },
65+
});
66+
const plainText = message.plainTextBuffer();
67+
const decoded = SignalService.Content.decode(plainText);
68+
69+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.deep.eq('Jane');
70+
expect(decoded.messageRequestResponse?.profile?.profilePicture).to.be.empty;
71+
expect(decoded.messageRequestResponse?.profileKey).to.be.empty;
72+
});
73+
74+
it('empty profileKey does not get included', () => {
75+
message = new MessageRequestResponse({
76+
timestamp: Date.now(),
77+
lokiProfile: { displayName: 'Jane', profileKey: new Uint8Array(0) },
78+
});
79+
const plainText = message.plainTextBuffer();
80+
const decoded = SignalService.Content.decode(plainText);
81+
82+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.eq('Jane');
83+
84+
expect(decoded.messageRequestResponse?.profile?.profilePicture).to.be.empty;
85+
expect(decoded.messageRequestResponse?.profileKey).to.be.empty;
86+
});
87+
88+
it('can create response with display name and profileKey and profileImage', () => {
89+
message = new MessageRequestResponse({
90+
timestamp: Date.now(),
91+
lokiProfile: {
92+
displayName: 'Jane',
93+
profileKey: new Uint8Array([1, 2, 3, 4, 5, 6]),
94+
avatarPointer: 'https://somevalidurl.com',
95+
},
96+
});
97+
const plainText = message.plainTextBuffer();
98+
const decoded = SignalService.Content.decode(plainText);
99+
100+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.deep.eq('Jane');
101+
102+
expect(decoded.messageRequestResponse?.profileKey).to.be.not.empty;
103+
104+
if (!decoded.messageRequestResponse?.profileKey?.buffer) {
105+
throw new Error('decoded.messageRequestResponse?.profileKey?.buffer should be set');
106+
}
107+
expect(decoded.messageRequestResponse?.profile?.profilePicture).to.be.eq(
108+
'https://somevalidurl.com'
109+
);
110+
// don't ask me why deep.eq ([1,2,3, ...]) gives nothing interesting but a 8192 buffer not matching
111+
expect(decoded.messageRequestResponse?.profileKey.length).to.be.eq(6);
112+
expect(decoded.messageRequestResponse?.profileKey[0]).to.be.eq(1);
113+
expect(decoded.messageRequestResponse?.profileKey[1]).to.be.eq(2);
114+
expect(decoded.messageRequestResponse?.profileKey[2]).to.be.eq(3);
115+
expect(decoded.messageRequestResponse?.profileKey[3]).to.be.eq(4);
116+
expect(decoded.messageRequestResponse?.profileKey[4]).to.be.eq(5);
117+
expect(decoded.messageRequestResponse?.profileKey[5]).to.be.eq(6);
118+
});
119+
120+
it('profileKey not included if profileUrl not set', () => {
121+
message = new MessageRequestResponse({
122+
timestamp: Date.now(),
123+
lokiProfile: { displayName: 'Jane', profileKey: new Uint8Array([1, 2, 3, 4, 5, 6]) },
124+
});
125+
const plainText = message.plainTextBuffer();
126+
const decoded = SignalService.Content.decode(plainText);
127+
128+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.deep.eq('Jane');
129+
130+
if (!decoded.messageRequestResponse?.profileKey?.buffer) {
131+
throw new Error('decoded.messageRequestResponse?.profileKey?.buffer should be set');
132+
}
133+
134+
expect(decoded.messageRequestResponse?.profile?.profilePicture).to.be.empty;
135+
expect(decoded.messageRequestResponse?.profileKey).to.be.empty;
136+
});
137+
138+
it('url not included if profileKey not set', () => {
139+
message = new MessageRequestResponse({
140+
timestamp: Date.now(),
141+
lokiProfile: {
142+
displayName: 'Jane',
143+
profileKey: null,
144+
avatarPointer: 'https://somevalidurl.com',
145+
},
146+
});
147+
const plainText = message.plainTextBuffer();
148+
const decoded = SignalService.Content.decode(plainText);
149+
150+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.deep.eq('Jane');
151+
152+
if (!decoded.messageRequestResponse?.profileKey?.buffer) {
153+
throw new Error('decoded.messageRequestResponse?.profileKey?.buffer should be set');
154+
}
155+
156+
expect(decoded.messageRequestResponse?.profile?.displayName).to.be.eq('Jane');
157+
expect(decoded.messageRequestResponse?.profile?.profilePicture).to.be.empty;
158+
expect(decoded.messageRequestResponse?.profileKey).to.be.empty;
159+
});
160+
});

0 commit comments

Comments
 (0)