Merge pull request #661 from sachaaaaa/encrypted_avatar

Encrypted profile pic
pull/680/head
Ryan Tharp 6 years ago committed by GitHub
commit a3abf3b14a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -933,7 +933,8 @@
avatarColor: conversation.getColor(), avatarColor: conversation.getColor(),
onOk: async (newName, avatar) => { onOk: async (newName, avatar) => {
let newAvatarPath = ''; let newAvatarPath = '';
let url = null;
let profileKey = null;
if (avatar) { if (avatar) {
const data = await readFile({ file: avatar }); const data = await readFile({ file: avatar });
@ -945,19 +946,30 @@
conversation.setLokiProfile({ displayName: newName }); conversation.setLokiProfile({ displayName: newName });
conversation.set('avatar', tempUrl); conversation.set('avatar', tempUrl);
const avatarPointer = await textsecure.messaging.uploadAvatar( // Encrypt with a new key every time
data profileKey = libsignal.crypto.getRandomBytes(32);
const encryptedData = await textsecure.crypto.encryptProfile(
data.data,
profileKey
); );
conversation.set('avatarPointer', avatarPointer.url); const avatarPointer = await textsecure.messaging.uploadAvatar({
...data,
data: encryptedData,
size: encryptedData.byteLength,
});
({ url } = avatarPointer);
storage.put('profileKey', profileKey);
const downloaded = await messageReceiver.downloadAttachment({ conversation.set('avatarPointer', url);
url: avatarPointer.url,
const upgraded = await Signal.Migrations.processNewAttachment({
isRaw: true, isRaw: true,
data: data.data,
url,
}); });
const upgraded = await Signal.Migrations.processNewAttachment(
downloaded
);
newAvatarPath = upgraded.path; newAvatarPath = upgraded.path;
} }
@ -973,6 +985,12 @@
// so we could disable this here // so we could disable this here
// or least it enable for the quickest response // or least it enable for the quickest response
window.lokiPublicChatAPI.setProfileName(newName); window.lokiPublicChatAPI.setProfileName(newName);
window
.getConversations()
.filter(convo => convo.isPublic() && !convo.isRss())
.forEach(convo =>
convo.trigger('ourAvatarChanged', { url, profileKey })
);
}, },
}); });
} }

@ -87,6 +87,7 @@
groupAdmins: [], groupAdmins: [],
isKickedFromGroup: false, isKickedFromGroup: false,
isOnline: false, isOnline: false,
profileSharing: false,
}; };
}, },
@ -156,6 +157,15 @@
this.on('expiration-change', this.updateAndMerge); this.on('expiration-change', this.updateAndMerge);
this.on('expired', this.onExpired); this.on('expired', this.onExpired);
this.on('ourAvatarChanged', avatar =>
this.updateAvatarOnPublicChat(avatar)
);
// Always share profile pics with public chats
if (this.isPublic) {
this.set('profileSharing', true);
}
const sealedSender = this.get('sealedSender'); const sealedSender = this.get('sealedSender');
if (sealedSender === undefined) { if (sealedSender === undefined) {
this.set({ sealedSender: SEALED_SENDER.UNKNOWN }); this.set({ sealedSender: SEALED_SENDER.UNKNOWN });
@ -846,14 +856,18 @@
} }
if (this.get('friendRequestStatus') !== newStatus) { if (this.get('friendRequestStatus') !== newStatus) {
this.set({ friendRequestStatus: newStatus }); this.set({ friendRequestStatus: newStatus });
if (newStatus === FriendRequestStatusEnum.friends) {
if (!blockSync) {
// Sync contact
this.wrapSend(textsecure.messaging.sendContactSyncMessage(this));
}
// Only enable sending profileKey after becoming friends
this.set({ profileSharing: true });
}
await window.Signal.Data.updateConversation(this.id, this.attributes, { await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
}); });
await this.updateTextInputState(); await this.updateTextInputState();
if (!blockSync && newStatus === FriendRequestStatusEnum.friends) {
// Sync contact
this.wrapSend(textsecure.messaging.sendContactSyncMessage(this));
}
} }
}, },
async updateGroupAdmins(groupAdmins) { async updateGroupAdmins(groupAdmins) {
@ -1493,6 +1507,11 @@
FriendRequestStatusEnum.pendingSend FriendRequestStatusEnum.pendingSend
); );
// Always share our profileKey in the friend request
// This will get added automatically after the FR
// is accepted, via the profileSharing flag
profileKey = storage.get('profileKey');
// Send the friend request! // Send the friend request!
messageWithSchema = await upgradeMessageSchema({ messageWithSchema = await upgradeMessageSchema({
type: 'friend-request', type: 'friend-request',
@ -1667,6 +1686,27 @@
); );
}, },
async updateAvatarOnPublicChat({ url, profileKey }) {
if (!this.isPublic()) {
return;
}
if (this.isRss()) {
return;
}
if (!this.get('profileSharing')) {
return;
}
if (profileKey && typeof profileKey !== 'string') {
// eslint-disable-next-line no-param-reassign
profileKey = window.Signal.Crypto.arrayBufferToBase64(profileKey);
}
const serverAPI = await lokiPublicChatAPI.findOrCreateServer(
this.get('server')
);
await serverAPI.setAvatar(url, profileKey);
},
async handleMessageSendResult({ async handleMessageSendResult({
failoverNumbers, failoverNumbers,
unidentifiedDeliveries, unidentifiedDeliveries,
@ -2272,9 +2312,8 @@
}); });
} }
if (newProfile.avatar) { // if set to null, it will show a jazzIcon
await this.setProfileAvatar({ path: newProfile.avatar }); await this.setProfileAvatar({ path: newProfile.avatar });
}
await this.updateProfileName(); await this.updateProfileName();
}, },

@ -14,6 +14,7 @@ const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s
const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s
const HOMESERVER_USER_ANNOTATION_TYPE = 'network.loki.messenger.homeserver'; const HOMESERVER_USER_ANNOTATION_TYPE = 'network.loki.messenger.homeserver';
const AVATAR_USER_ANNOTATION_TYPE = 'network.loki.messenger.avatar';
const MESSAGE_ATTACHMENT_TYPE = 'net.app.core.oembed'; const MESSAGE_ATTACHMENT_TYPE = 'net.app.core.oembed';
const LOKI_ATTACHMENT_TYPE = 'attachment'; const LOKI_ATTACHMENT_TYPE = 'attachment';
const LOKI_PREVIEW_TYPE = 'preview'; const LOKI_PREVIEW_TYPE = 'preview';
@ -133,6 +134,17 @@ class LokiAppDotNetAPI extends EventEmitter {
}) })
); );
} }
async setAvatar(url, profileKey) {
await Promise.all(
this.servers.map(async server => {
// this may fail
// but we can't create a sql table to remember to retry forever
// I think we just silently fail for now
await server.setAvatar(url, profileKey);
})
);
}
} }
class LokiAppDotNetServerAPI { class LokiAppDotNetServerAPI {
@ -254,6 +266,14 @@ class LokiAppDotNetServerAPI {
return res.response.data.annotations || []; return res.response.data.annotations || [];
} }
async setAvatar(url, profileKey) {
let value = null;
if (url && profileKey) {
value = { url, profileKey };
}
return this.setSelfAnnotation(AVATAR_USER_ANNOTATION_TYPE, value);
}
// get active token for this server // get active token for this server
async getOrRefreshServerToken(forceRefresh = false) { async getOrRefreshServerToken(forceRefresh = false) {
let token; let token;
@ -956,7 +976,16 @@ class LokiPublicChannelAPI {
} }
// timestamp is the only required field we've had since the first deployed version // timestamp is the only required field we've had since the first deployed version
const { timestamp, quote, avatar } = noteValue; const { timestamp, quote } = noteValue;
let profileKey = null;
let avatar = null;
const avatarNote = adnMessage.user.annotations.find(
note => note.type === AVATAR_USER_ANNOTATION_TYPE
);
if (avatarNote) {
({ profileKey, url: avatar } = avatarNote.value);
}
if (quote) { if (quote) {
// TODO: Enable quote attachments again using proper ADN style // TODO: Enable quote attachments again using proper ADN style
@ -1020,6 +1049,7 @@ class LokiPublicChannelAPI {
preview, preview,
quote, quote,
avatar, avatar,
profileKey,
}; };
} }
@ -1088,12 +1118,21 @@ class LokiPublicChannelAPI {
return false; // Invalid or delete message return false; // Invalid or delete message
} }
const pubKey = adnMessage.user.username;
const messengerData = await this.getMessengerData(adnMessage); const messengerData = await this.getMessengerData(adnMessage);
if (messengerData === false) { if (messengerData === false) {
return false; return false;
} }
const { timestamp, quote, attachments, preview } = messengerData; const {
timestamp,
quote,
attachments,
preview,
avatar,
profileKey,
} = messengerData;
if (!timestamp) { if (!timestamp) {
return false; // Invalid message return false; // Invalid message
} }
@ -1101,7 +1140,7 @@ class LokiPublicChannelAPI {
// Duplicate check // Duplicate check
const isDuplicate = message => { const isDuplicate = message => {
// The username in this case is the users pubKey // The username in this case is the users pubKey
const sameUsername = message.username === adnMessage.user.username; const sameUsername = message.username === pubKey;
const sameText = message.text === adnMessage.text; const sameText = message.text === adnMessage.text;
// Don't filter out messages that are too far apart from each other // Don't filter out messages that are too far apart from each other
const timestampsSimilar = const timestampsSimilar =
@ -1121,7 +1160,7 @@ class LokiPublicChannelAPI {
this.lastMessagesCache = [ this.lastMessagesCache = [
...this.lastMessagesCache, ...this.lastMessagesCache,
{ {
username: adnMessage.user.username, username: pubKey,
text: adnMessage.text, text: adnMessage.text,
timestamp, timestamp,
}, },
@ -1130,7 +1169,7 @@ class LokiPublicChannelAPI {
const from = adnMessage.user.name || 'Anonymous'; // profileName const from = adnMessage.user.name || 'Anonymous'; // profileName
// if us // if us
if (adnMessage.user.username === ourNumber) { if (pubKey === ourNumber) {
// update the last name we saw from ourself // update the last name we saw from ourself
lastProfileName = from; lastProfileName = from;
} }
@ -1151,12 +1190,8 @@ class LokiPublicChannelAPI {
if (homeServerPubKeys[homeServer] === undefined) { if (homeServerPubKeys[homeServer] === undefined) {
homeServerPubKeys[homeServer] = []; homeServerPubKeys[homeServer] = [];
} }
if ( if (homeServerPubKeys[homeServer].indexOf(`@${pubKey}`) === -1) {
homeServerPubKeys[homeServer].indexOf( homeServerPubKeys[homeServer].push(`@${pubKey}`);
`@${adnMessage.user.username}`
) === -1
) {
homeServerPubKeys[homeServer].push(`@${adnMessage.user.username}`);
} }
// generate signal message object // generate signal message object
@ -1164,7 +1199,7 @@ class LokiPublicChannelAPI {
serverId: adnMessage.id, serverId: adnMessage.id,
clientVerified: true, clientVerified: true,
friendRequest: false, friendRequest: false,
source: adnMessage.user.username, source: pubKey,
sourceDevice: 1, sourceDevice: 1,
timestamp, timestamp,
@ -1181,7 +1216,7 @@ class LokiPublicChannelAPI {
}, },
flags: 0, flags: 0,
expireTimer: 0, expireTimer: 0,
profileKey: null, profileKey,
timestamp, timestamp,
received_at: receivedAt, received_at: receivedAt,
sent_at: timestamp, sent_at: timestamp,
@ -1190,6 +1225,7 @@ class LokiPublicChannelAPI {
preview, preview,
profile: { profile: {
displayName: from, displayName: from,
avatar,
}, },
}, },
}; };
@ -1209,8 +1245,6 @@ class LokiPublicChannelAPI {
// slave to primary map for this group of messages // slave to primary map for this group of messages
let slavePrimaryMap = {}; let slavePrimaryMap = {};
// pubKey to avatar
let avatarMap = {};
// reduce list of servers into verified maps and keys // reduce list of servers into verified maps and keys
const verifiedPrimaryPKs = await Object.keys(homeServerPubKeys).reduce( const verifiedPrimaryPKs = await Object.keys(homeServerPubKeys).reduce(
@ -1229,53 +1263,20 @@ class LokiPublicChannelAPI {
// should not be any collisions, since each pubKey can only have one home server // should not be any collisions, since each pubKey can only have one home server
slavePrimaryMap = { ...slavePrimaryMap, ...result.slaveMap }; slavePrimaryMap = { ...slavePrimaryMap, ...result.slaveMap };
// merge this servers avatarMap into our map
// again shouldn't be any collisions
avatarMap = { ...avatarMap, ...serverAPI.avatarMap };
// copy verified pub keys into result // copy verified pub keys into result
return curVal.concat(result.verifiedPrimaryPKs); return curVal.concat(result.verifiedPrimaryPKs);
}, },
[] []
); );
// sort pending messages by if slave device or not // filter out invalid messages
/* eslint-disable no-param-reassign */ pendingMessages = pendingMessages.filter(messageData => !!messageData);
const slaveMessages = pendingMessages // separate messages coming from primary and secondary devices
.filter(messageData => !!messageData) // filter out false messages const [primaryMessages, slaveMessages] = _.partition(pendingMessages, message => !(message.source in slavePrimaryMap));
.reduce((retval, messageData) => { // process primary devices' message directly
// if a known slave primaryMessages.forEach(message => this.serverAPI.chatAPI.emit('publicMessage', {
if (slavePrimaryMap[messageData.source]) { message,
// pop primary device avatars in }));
if (avatarMap[slavePrimaryMap[messageData.source]]) {
// modify messageData for user's avatar
messageData.message.profile.avatar =
avatarMap[slavePrimaryMap[messageData.source]];
}
// delay sending the message
if (retval[messageData.source] === undefined) {
retval[messageData.source] = [messageData];
} else {
retval[messageData.source].push(messageData);
}
} else {
// not from a paired/slave/unregistered device
// pop current device avatars in
if (avatarMap[messageData.source]) {
// modify messageData for user's avatar
messageData.message.profile.avatar = avatarMap[messageData.source];
}
// send event now
this.serverAPI.chatAPI.emit('publicMessage', {
message: messageData,
});
}
return retval;
}, {});
/* eslint-enable no-param-reassign */
pendingMessages = []; // allow memory to be freed pendingMessages = []; // allow memory to be freed
@ -1288,7 +1289,19 @@ class LokiPublicChannelAPI {
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
this.primaryUserProfileName = verifiedDeviceResults.reduce( this.primaryUserProfileName = verifiedDeviceResults.reduce(
(mapOut, user) => { (mapOut, user) => {
mapOut[user.username] = user.name; let avatar = null;
let profileKey = null;
const avatarNote = user.annotations.find(
note => note.type === AVATAR_USER_ANNOTATION_TYPE
);
if (avatarNote) {
({ profileKey, url: avatar } = avatarNote.value);
}
mapOut[user.username] = {
name: user.name,
avatar,
profileKey,
};
return mapOut; return mapOut;
}, },
{} {}
@ -1312,9 +1325,12 @@ class LokiPublicChannelAPI {
if (slavePrimaryMap[messageData.source]) { if (slavePrimaryMap[messageData.source]) {
// rewrite source, profile // rewrite source, profile
messageData.source = primaryPubKey; messageData.source = primaryPubKey;
messageData.message.profile.displayName = this.primaryUserProfileName[ const { name, avatar, profileKey } = this.primaryUserProfileName[
primaryPubKey primaryPubKey
]; ];
messageData.message.profile.displayName = name;
messageData.message.profile.avatar = avatar;
messageData.message.profileKey = profileKey;
} }
this.serverAPI.chatAPI.emit('publicMessage', { this.serverAPI.chatAPI.emit('publicMessage', {
message: messageData, message: messageData,
@ -1426,8 +1442,6 @@ class LokiPublicChannelAPI {
LokiPublicChannelAPI.getAnnotationFromPreview LokiPublicChannelAPI.getAnnotationFromPreview
); );
const avatarAnnotation = data.profile.avatar || null;
const payload = { const payload = {
text, text,
annotations: [ annotations: [
@ -1435,8 +1449,6 @@ class LokiPublicChannelAPI {
type: 'network.loki.messenger.publicChat', type: 'network.loki.messenger.publicChat',
value: { value: {
timestamp: messageTimeStamp, timestamp: messageTimeStamp,
// can remove after this release
avatar: avatarAnnotation,
}, },
}, },
...attachmentAnnotations, ...attachmentAnnotations,

@ -17,6 +17,10 @@ class LokiFileServerInstance {
this._adnApi = new LokiAppDotNetAPI(ourKey); this._adnApi = new LokiAppDotNetAPI(ourKey);
this.avatarMap = {}; this.avatarMap = {};
} }
// FIXME: this is not file-server specific
// and is currently called by LokiAppDotNetAPI.
// LokiAppDotNetAPI (base) should not know about LokiFileServer.
async establishConnection(serverUrl) { async establishConnection(serverUrl) {
// FIXME: we don't always need a token... // FIXME: we don't always need a token...
this._server = await this._adnApi.findOrCreateServer(serverUrl); this._server = await this._adnApi.findOrCreateServer(serverUrl);

@ -202,11 +202,17 @@
if (isGrant) { if (isGrant) {
// Send profile name to secondary device // Send profile name to secondary device
const lokiProfile = ourConversation.getLokiProfile(); const lokiProfile = ourConversation.getLokiProfile();
// profile.avatar is the path to the local image
// replace with the avatar URL
const avatarPointer = ourConversation.get('avatarPointer');
lokiProfile.avatar = avatarPointer;
const profile = new textsecure.protobuf.DataMessage.LokiProfile( const profile = new textsecure.protobuf.DataMessage.LokiProfile(
lokiProfile lokiProfile
); );
const profileKey = window.storage.get('profileKey');
const dataMessage = new textsecure.protobuf.DataMessage({ const dataMessage = new textsecure.protobuf.DataMessage({
profile, profile,
profileKey,
}); });
// Attach contact list // Attach contact list
const conversations = await window.Signal.Data.getConversationsWithFriendStatus( const conversations = await window.Signal.Data.getConversationsWithFriendStatus(

@ -172,7 +172,11 @@ MessageReceiver.prototype.extend({
message.source, message.source,
'private' 'private'
); );
await this.updateProfile(conversation, message.message.profile); await this.updateProfile(
conversation,
message.message.profile,
message.message.profileKey
);
} }
const ev = new Event('message'); const ev = new Event('message');
@ -1012,6 +1016,8 @@ MessageReceiver.prototype.extend({
throw e; throw e;
} }
}, },
// handle a SYNC message for a message
// sent by another device
handleSentMessage(envelope, sentContainer, msg) { handleSentMessage(envelope, sentContainer, msg) {
const { const {
destination, destination,
@ -1029,9 +1035,10 @@ MessageReceiver.prototype.extend({
this.processDecrypted(envelope, msg).then(message => { this.processDecrypted(envelope, msg).then(message => {
const groupId = message.group && message.group.id; const groupId = message.group && message.group.id;
const isBlocked = this.isGroupBlocked(groupId); const isBlocked = this.isGroupBlocked(groupId);
const primaryDevicePubKey = window.storage.get('primaryDevicePubKey');
const isMe = const isMe =
envelope.source === textsecure.storage.user.getNumber() || envelope.source === textsecure.storage.user.getNumber() ||
envelope.source === window.storage.get('primaryDevicePubKey'); envelope.source === primaryDevicePubKey;
const isLeavingGroup = Boolean( const isLeavingGroup = Boolean(
message.group && message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
@ -1046,6 +1053,16 @@ MessageReceiver.prototype.extend({
return this.removeFromCache(envelope); return this.removeFromCache(envelope);
} }
// handle profileKey and avatar updates
if (envelope.source === primaryDevicePubKey) {
const { profileKey, profile } = message;
const primaryConversation = ConversationController.get(
primaryDevicePubKey
);
//
this.updateProfile(primaryConversation, profile, profileKey);
}
const ev = new Event('sent'); const ev = new Event('sent');
ev.confirm = this.removeFromCache.bind(this, envelope); ev.confirm = this.removeFromCache.bind(this, envelope);
ev.data = { ev.data = {
@ -1127,12 +1144,13 @@ MessageReceiver.prototype.extend({
); );
primaryConversation.trigger('change'); primaryConversation.trigger('change');
Whisper.events.trigger('secondaryDeviceRegistration'); Whisper.events.trigger('secondaryDeviceRegistration');
// Update profile name // Update profile
if (dataMessage && dataMessage.profile) { if (dataMessage) {
const { profile, profileKey } = dataMessage;
const ourNumber = window.storage.get('primaryDevicePubKey'); const ourNumber = window.storage.get('primaryDevicePubKey');
const me = window.ConversationController.get(ourNumber); const me = window.ConversationController.get(ourNumber);
if (me) { if (me) {
me.setLokiProfile(dataMessage.profile); this.updateProfile(me, profile, profileKey);
} }
} }
// Update contact list // Update contact list
@ -1238,7 +1256,7 @@ MessageReceiver.prototype.extend({
return true; return true;
}, },
async updateProfile(conversation, profile) { async updateProfile(conversation, profile, profileKey) {
// Retain old values unless changed: // Retain old values unless changed:
const newProfile = conversation.get('profile') || {}; const newProfile = conversation.get('profile') || {};
@ -1252,17 +1270,40 @@ MessageReceiver.prototype.extend({
if (needsUpdate) { if (needsUpdate) {
conversation.set('avatarPointer', profile.avatar); conversation.set('avatarPointer', profile.avatar);
conversation.set('profileKey', profileKey);
const downloaded = await this.downloadAttachment({ const downloaded = await this.downloadAttachment({
url: profile.avatar, url: profile.avatar,
isRaw: true, isRaw: true,
}); });
const upgraded = await Signal.Migrations.processNewAttachment( // null => use jazzicon
downloaded let path = null;
); if (profileKey) {
newProfile.avatar = upgraded.path; // Convert profileKey to ArrayBuffer, if needed
const encoding = typeof profileKey === 'string' ? 'base64' : null;
try {
const profileKeyArrayBuffer = dcodeIO.ByteBuffer.wrap(
profileKey,
encoding
).toArrayBuffer();
const decryptedData = await textsecure.crypto.decryptProfile(
downloaded.data,
profileKeyArrayBuffer
);
const upgraded = await Signal.Migrations.processNewAttachment({
...downloaded,
data: decryptedData,
});
({ path } = upgraded);
} catch (e) {
window.log.error(`Could not decrypt profile image: ${e}`);
}
}
newProfile.avatar = path;
} }
} else {
newProfile.avatar = null;
} }
await conversation.setLokiProfile(newProfile); await conversation.setLokiProfile(newProfile);
@ -1361,7 +1402,11 @@ MessageReceiver.prototype.extend({
// Check if we need to update any profile names // Check if we need to update any profile names
if (!isMe && conversation) { if (!isMe && conversation) {
if (message.profile) { if (message.profile) {
await this.updateProfile(conversation, message.profile); await this.updateProfile(
conversation,
message.profile,
message.profileKey
);
} }
} }

@ -191,8 +191,7 @@ MessageSender.prototype = {
async makeAttachmentPointer( async makeAttachmentPointer(
attachment, attachment,
publicServer = null, publicServer = null,
isRaw = false, { isRaw = false, isAvatar = false }
isAvatar = false
) { ) {
if (typeof attachment !== 'object' || attachment == null) { if (typeof attachment !== 'object' || attachment == null) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
@ -211,13 +210,7 @@ MessageSender.prototype = {
const proto = new textsecure.protobuf.AttachmentPointer(); const proto = new textsecure.protobuf.AttachmentPointer();
let attachmentData; let attachmentData;
let server; const server = publicServer || this.server;
if (publicServer) {
server = publicServer;
} else {
({ server } = this);
}
if (publicServer || isRaw) { if (publicServer || isRaw) {
attachmentData = attachment.data; attachmentData = attachment.data;
@ -572,7 +565,12 @@ MessageSender.prototype = {
}, },
uploadAvatar(attachment) { uploadAvatar(attachment) {
return this.makeAttachmentPointer(attachment, null, true, true); // isRaw is true since the data is already encrypted
// and doesn't need to be encrypted again
return this.makeAttachmentPointer(attachment, null, {
isRaw: true,
isAvatar: true,
});
}, },
sendRequestConfigurationSyncMessage(options) { sendRequestConfigurationSyncMessage(options) {
@ -926,7 +924,9 @@ MessageSender.prototype = {
getOurProfile() { getOurProfile() {
try { try {
const ourNumber = textsecure.storage.user.getNumber(); // Secondary devices have their profile stored
// in their primary device's conversation
const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = window.ConversationController.get(ourNumber); const conversation = window.ConversationController.get(ourNumber);
return conversation.getLokiProfile(); return conversation.getLokiProfile();
} catch (e) { } catch (e) {

@ -333,13 +333,6 @@ export class MainHeader extends React.Component<Props, any> {
name: i18n('copyPublicKey'), name: i18n('copyPublicKey'),
onClick: onCopyPublicKey, onClick: onCopyPublicKey,
}, },
{
id: 'editProfile',
name: i18n('editProfile'),
onClick: () => {
trigger('onEditProfile');
},
},
{ {
id: 'showSeed', id: 'showSeed',
name: i18n('showSeed'), name: i18n('showSeed'),
@ -396,6 +389,14 @@ export class MainHeader extends React.Component<Props, any> {
} }
if (!isSecondaryDevice) { if (!isSecondaryDevice) {
// insert as second element
menuItems.splice(1, 0, {
id: 'editProfile',
name: i18n('editProfile'),
onClick: () => {
trigger('onEditProfile');
},
});
menuItems.push({ menuItems.push({
id: 'pairNewDevice', id: 'pairNewDevice',
name: 'Device Pairing', name: 'Device Pairing',

Loading…
Cancel
Save