Merge pull request #1176 from Mikunj/multi-device-protocol

Multidevice Protocol Refactor
pull/1180/head
Mikunj Varsani 5 years ago committed by GitHub
commit ce868456c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -74,12 +74,8 @@ module.exports = {
removeAllContactSignedPreKeys, removeAllContactSignedPreKeys,
createOrUpdatePairingAuthorisation, createOrUpdatePairingAuthorisation,
removePairingAuthorisationForSecondaryPubKey, getPairingAuthorisationsFor,
getAuthorisationForSecondaryPubKey, removePairingAuthorisationsFor,
getGrantAuthorisationsForPrimaryPubKey,
getSecondaryDevicesFor,
getPrimaryDeviceFor,
getPairedDevicesFor,
createOrUpdateItem, createOrUpdateItem,
getItemById, getItemById,
@ -1506,42 +1502,19 @@ async function removeAllSignedPreKeys() {
const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations'; const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations';
const GUARD_NODE_TABLE = 'guardNodes'; async function getPairingAuthorisationsFor(pubKey) {
async function getAuthorisationForSecondaryPubKey(pubKey, options) {
const granted = options && options.granted;
let filter = '';
if (granted) {
filter = 'AND isGranted = 1';
}
const row = await db.get(
`SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey ${filter};`,
{
$secondaryDevicePubKey: pubKey,
}
);
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function getGrantAuthorisationsForPrimaryPubKey(primaryDevicePubKey) {
const rows = await db.all( const rows = await db.all(
`SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey AND isGranted = 1 ORDER BY secondaryDevicePubKey ASC;`, `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`,
{ { $pubKey: pubKey }
$primaryDevicePubKey: primaryDevicePubKey,
}
); );
return map(rows, row => jsonToObject(row.json));
return rows.map(row => jsonToObject(row.json));
} }
async function createOrUpdatePairingAuthorisation(data) { async function createOrUpdatePairingAuthorisation(data) {
const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data; const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data;
// remove any existing authorisation for this pubkey (we allow only one secondary device for now) // remove any existing authorisation for this pubkey (we allow only one secondary device for now)
await removePairingAuthorisationForPrimaryPubKey(primaryDevicePubKey); await removePairingAuthorisationsFor(primaryDevicePubKey);
await db.run( await db.run(
`INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} ( `INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} (
@ -1564,30 +1537,16 @@ async function createOrUpdatePairingAuthorisation(data) {
); );
} }
async function removePairingAuthorisationForPrimaryPubKey(pubKey) { async function removePairingAuthorisationsFor(pubKey) {
await db.run(
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey;`,
{
$primaryDevicePubKey: pubKey,
}
);
}
async function removePairingAuthorisationForSecondaryPubKey(pubKey) {
await db.run( await db.run(
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey;`, `DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`,
{ {
$secondaryDevicePubKey: pubKey, $pubKey: pubKey,
} }
); );
} }
async function getSecondaryDevicesFor(primaryDevicePubKey) { const GUARD_NODE_TABLE = 'guardNodes';
const authorisations = await getGrantAuthorisationsForPrimaryPubKey(
primaryDevicePubKey
);
return map(authorisations, row => row.secondaryDevicePubKey);
}
async function getGuardNodes() { async function getGuardNodes() {
const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`); const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`);
@ -1620,42 +1579,8 @@ async function updateGuardNodes(nodes) {
await db.run('END TRANSACTION;'); await db.run('END TRANSACTION;');
} }
async function getPrimaryDeviceFor(secondaryDevicePubKey) {
const row = await db.get(
`SELECT primaryDevicePubKey FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey AND isGranted = 1;`,
{
$secondaryDevicePubKey: secondaryDevicePubKey,
}
);
if (!row) {
return null;
}
return row.primaryDevicePubKey;
}
// Return all the paired pubkeys for a specific pubkey (excluded), // Return all the paired pubkeys for a specific pubkey (excluded),
// irrespective of their Primary or Secondary status. // irrespective of their Primary or Secondary status.
async function getPairedDevicesFor(pubKey) {
let results = [];
// get primary pubkey (only works if the pubkey is a secondary pubkey)
const primaryPubKey = await getPrimaryDeviceFor(pubKey);
if (primaryPubKey) {
results.push(primaryPubKey);
}
// get secondary pubkeys (only works if the pubkey is a primary pubkey)
const secondaryPubKeys = await getSecondaryDevicesFor(
primaryPubKey || pubKey
);
results = results.concat(secondaryPubKeys);
// ensure the input pubkey is not in the results
results = results.filter(x => x !== pubKey);
return results;
}
const ITEMS_TABLE = 'items'; const ITEMS_TABLE = 'items';
async function createOrUpdateItem(data) { async function createOrUpdateItem(data) {

@ -9,6 +9,7 @@
textsecure, textsecure,
Whisper, Whisper,
libloki, libloki,
libsession,
libsignal, libsignal,
StringView, StringView,
BlockedNumberController, BlockedNumberController,
@ -1425,7 +1426,7 @@
Whisper.events.on('devicePairingRequestRejected', async pubKey => { Whisper.events.on('devicePairingRequestRejected', async pubKey => {
await libloki.storage.removeContactPreKeyBundle(pubKey); await libloki.storage.removeContactPreKeyBundle(pubKey);
await libloki.storage.removePairingAuthorisationForSecondaryPubKey( await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
pubKey pubKey
); );
}); });
@ -1435,8 +1436,7 @@
if (isSecondaryDevice) { if (isSecondaryDevice) {
return; return;
} }
await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
await libloki.storage.removePairingAuthorisationForSecondaryPubKey(
pubKey pubKey
); );
await window.lokiFileServerAPI.updateOurDeviceMapping(); await window.lokiFileServerAPI.updateOurDeviceMapping();
@ -1761,16 +1761,15 @@
return; return;
} }
let primaryDevice = null; // A sender here could be referring to a group.
const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( // Groups don't have primary devices so we need to take that into consideration.
sender const user = libsession.Types.PubKey.from(sender);
); const primaryDevice = user
if (authorisation) { ? await libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(user)
primaryDevice = authorisation.primaryDevicePubKey; : null;
}
const conversation = ConversationController.get( const conversation = ConversationController.get(
groupId || primaryDevice || sender groupId || (primaryDevice && primaryDevice.key) || sender
); );
if (conversation) { if (conversation) {
@ -1826,25 +1825,21 @@
activeAt = activeAt || Date.now(); activeAt = activeAt || Date.now();
} }
const ourPrimaryKey = window.storage.get('primaryDevicePubKey'); const ourPrimaryKey = window.storage.get('primaryDevicePubKey');
const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( if (ourPrimaryKey) {
ourPrimaryKey const secondaryDevices = await libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(
); ourPrimaryKey
// TODO: We should probably just *not* send any secondary devices and );
// just load them all and send FRs when we get the mapping if (secondaryDevices.some(device => device.key === id)) {
const isOurSecondaryDevice = await conversation.setSecondaryStatus(true, ourPrimaryKey);
id !== ourPrimaryKey && }
ourDevices &&
ourDevices.some(devicePubKey => devicePubKey === id);
if (isOurSecondaryDevice) {
await conversation.setSecondaryStatus(true, ourPrimaryKey);
} }
const otherDevices = await libloki.storage.getPairedDevicesFor(id); const devices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices(
const devices = [id, ...otherDevices]; id
);
const deviceConversations = await Promise.all( const deviceConversations = await Promise.all(
devices.map(d => devices.map(d =>
ConversationController.getOrCreateAndWait(d, 'private') ConversationController.getOrCreateAndWait(d.key, 'private')
) )
); );
deviceConversations.forEach(device => { deviceConversations.forEach(device => {

@ -4,7 +4,6 @@
ConversationController, ConversationController,
MessageController, MessageController,
_, _,
libloki,
*/ */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
@ -31,18 +30,15 @@
this.remove(receipts); this.remove(receipts);
return receipts; return receipts;
}, },
async getTargetMessage(source, messages) { async getTargetMessage(originalSource, messages) {
if (messages.length === 0) { if (messages.length === 0) {
return null; return null;
} }
const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(
source originalSource
); );
if (authorisation) { const source = primary.key;
// eslint-disable-next-line no-param-reassign
source = authorisation.primaryDevicePubKey;
}
const message = messages.find( const message = messages.find(
item => !item.isIncoming() && source === item.get('conversationId') item => !item.isIncoming() && source === item.get('conversationId')

@ -211,10 +211,9 @@
return true; return true;
} }
const ourDevices = await window.libloki.storage.getPairedDevicesFor( return window.libsession.Protocols.MultiDeviceProtocol.isOurDevice(
this.ourNumber this.id
); );
return ourDevices.includes(this.id);
}, },
isOurLocalDevice() { isOurLocalDevice() {
return this.id === this.ourNumber; return this.id === this.ourNumber;
@ -876,15 +875,19 @@
// This is already the primary conversation // This is already the primary conversation
return this; return this;
} }
const authorisation = await window.libloki.storage.getAuthorisationForSecondaryPubKey(
this.id const device = window.libsession.Types.PubKey.from(this.id);
); if (device) {
if (authorisation) { const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(
device
);
return ConversationController.getOrCreateAndWait( return ConversationController.getOrCreateAndWait(
authorisation.primaryDevicePubKey, primary.key,
'private' 'private'
); );
} }
// Something funky has happened // Something funky has happened
return this; return this;
}, },

@ -1,4 +1,3 @@
type MessageModelType = 'incoming' | 'outgoing' | 'friend-request'; type MessageModelType = 'incoming' | 'outgoing' | 'friend-request';
export type EndSessionType = 'done' | 'ongoing'; export type EndSessionType = 'done' | 'ongoing';

@ -46,7 +46,7 @@ type PairingAuthorisation = {
primaryDevicePubKey: string; primaryDevicePubKey: string;
secondaryDevicePubKey: string; secondaryDevicePubKey: string;
requestSignature: ArrayBuffer; requestSignature: ArrayBuffer;
grantSignature: ArrayBuffer | null; grantSignature?: ArrayBuffer;
}; };
type GuardNode = { type GuardNode = {
@ -153,25 +153,10 @@ export function removeAllContactSignedPreKeys(): Promise<void>;
export function createOrUpdatePairingAuthorisation( export function createOrUpdatePairingAuthorisation(
data: PairingAuthorisation data: PairingAuthorisation
): Promise<void>; ): Promise<void>;
export function removePairingAuthorisationForSecondaryPubKey( export function getPairingAuthorisationsFor(
pubKey: string
): Promise<void>;
export function getGrantAuthorisationsForPrimaryPubKey(
pubKey: string pubKey: string
): Promise<Array<PairingAuthorisation>>; ): Promise<Array<PairingAuthorisation>>;
export function getGrantAuthorisationForSecondaryPubKey( export function removePairingAuthorisationsFor(pubKey: string): Promise<void>;
pubKey: string
): Promise<PairingAuthorisation | null>;
export function getAuthorisationForSecondaryPubKey(
pubKey: string
): Promise<PairingAuthorisation | null>;
export function getSecondaryDevicesFor(
primaryDevicePubKey: string
): Promise<Array<string>>;
export function getPrimaryDeviceFor(
secondaryDevicePubKey: string
): Promise<string | null>;
export function getPairedDevicesFor(pubKey: string): Promise<Array<string>>;
// Guard Nodes // Guard Nodes
export function getGuardNodes(): Promise<GuardNode>; export function getGuardNodes(): Promise<GuardNode>;

@ -89,13 +89,8 @@ module.exports = {
removeAllContactSignedPreKeys, removeAllContactSignedPreKeys,
createOrUpdatePairingAuthorisation, createOrUpdatePairingAuthorisation,
removePairingAuthorisationForSecondaryPubKey, getPairingAuthorisationsFor,
getGrantAuthorisationForSecondaryPubKey, removePairingAuthorisationsFor,
getAuthorisationForSecondaryPubKey,
getGrantAuthorisationsForPrimaryPubKey,
getSecondaryDevicesFor,
getPrimaryDeviceFor,
getPairedDevicesFor,
getGuardNodes, getGuardNodes,
updateGuardNodes, updateGuardNodes,
@ -631,29 +626,20 @@ async function createOrUpdatePairingAuthorisation(data) {
}); });
} }
async function removePairingAuthorisationForSecondaryPubKey(pubKey) { async function getPairingAuthorisationsFor(pubKey) {
if (!pubKey) { const authorisations = channels.getPairingAuthorisationsFor(pubKey);
return;
}
await channels.removePairingAuthorisationForSecondaryPubKey(pubKey);
}
async function getGrantAuthorisationForSecondaryPubKey(pubKey) {
return channels.getAuthorisationForSecondaryPubKey(pubKey, {
granted: true,
});
}
async function getGrantAuthorisationsForPrimaryPubKey(pubKey) { return authorisations.map(authorisation => ({
return channels.getGrantAuthorisationsForPrimaryPubKey(pubKey); ...authorisation,
requestSignature: base64ToArrayBuffer(authorisation.requestSignature),
grantSignature: authorisation.grantSignature
? base64ToArrayBuffer(authorisation.grantSignature)
: undefined,
}));
} }
function getAuthorisationForSecondaryPubKey(pubKey) { async function removePairingAuthorisationsFor(pubKey) {
return channels.getAuthorisationForSecondaryPubKey(pubKey); await channels.removePairingAuthorisationsFor(pubKey);
}
function getSecondaryDevicesFor(primaryDevicePubKey) {
return channels.getSecondaryDevicesFor(primaryDevicePubKey);
} }
function getGuardNodes() { function getGuardNodes() {
@ -664,14 +650,6 @@ function updateGuardNodes(nodes) {
return channels.updateGuardNodes(nodes); return channels.updateGuardNodes(nodes);
} }
function getPrimaryDeviceFor(secondaryDevicePubKey) {
return channels.getPrimaryDeviceFor(secondaryDevicePubKey);
}
function getPairedDevicesFor(pubKey) {
return channels.getPairedDevicesFor(pubKey);
}
// Items // Items
const ITEM_KEYS = { const ITEM_KEYS = {

@ -0,0 +1,16 @@
interface FileServerPairingAuthorisation {
primaryDevicePubKey: string;
secondaryDevicePubKey: string;
requestSignature: string; // base64
grantSignature: string; // base64
}
interface DeviceMappingAnnotation {
isPrimary: string;
authorisations: Array<FileServerPairingAuthorisation>;
}
interface LokiFileServerInstance {
getUserDeviceMapping(pubKey: string): Promise<DeviceMappingAnnotation>;
clearOurDeviceMappingAnnotations(): Promise<void>;
}

@ -1,6 +1,5 @@
/* global log, libloki, window */ /* global log, libloki, window */
/* global storage: false */ /* global storage: false */
/* global Signal: false */
/* global log: false */ /* global log: false */
const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
@ -220,16 +219,10 @@ class LokiHomeServerInstance extends LokiFileServerInstance {
async updateOurDeviceMapping() { async updateOurDeviceMapping() {
const isPrimary = !storage.get('isSecondaryDevice'); const isPrimary = !storage.get('isSecondaryDevice');
let authorisations; const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(
if (isPrimary) { this.ourKey
authorisations = await Signal.Data.getGrantAuthorisationsForPrimaryPubKey( );
this.ourKey
);
} else {
authorisations = [
await Signal.Data.getGrantAuthorisationForSecondaryPubKey(this.ourKey),
];
}
return this._setOurDeviceMapping(authorisations, isPrimary); return this._setOurDeviceMapping(authorisations, isPrimary);
} }

@ -1,4 +1,4 @@
/* global Whisper, i18n, textsecure, libloki, _ */ /* global Whisper, i18n, textsecure, _ */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
@ -197,7 +197,7 @@
// exists in group, but hasn't yet synced with its other devices. // exists in group, but hasn't yet synced with its other devices.
const getDevicesForRemoved = async () => { const getDevicesForRemoved = async () => {
const promises = notPresentInNew.map(member => const promises = notPresentInNew.map(member =>
libloki.storage.getPairedDevicesFor(member) window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(member)
); );
const devices = _.flatten(await Promise.all(promises)); const devices = _.flatten(await Promise.all(promises));

@ -74,15 +74,6 @@
} }
} }
// Returns the primary device pubkey for this secondary device pubkey
// or the same pubkey if there is no other device
async function getPrimaryDevicePubkey(pubKey) {
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey(
pubKey
);
return authorisation ? authorisation.primaryDevicePubKey : pubKey;
}
async function sendSessionEstablishedMessage(pubKey) { async function sendSessionEstablishedMessage(pubKey) {
// This message shouldn't be routed through multi-device. // This message shouldn't be routed through multi-device.
// It needs to go directly to the pubKey specified. // It needs to go directly to the pubKey specified.
@ -330,7 +321,6 @@
createContactSyncProtoMessage, createContactSyncProtoMessage,
createGroupSyncProtoMessage, createGroupSyncProtoMessage,
createOpenGroupsSyncProtoMessage, createOpenGroupsSyncProtoMessage,
getPrimaryDevicePubkey,
debug, debug,
}; };
})(); })();

@ -1,13 +1,9 @@
/* global window, libsignal, textsecure, Signal, /* global window, libsignal, textsecure */
lokiFileServerAPI, ConversationController */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
window.libloki = window.libloki || {}; window.libloki = window.libloki || {};
const timers = {};
const REFRESH_DELAY = 60 * 1000;
async function getPreKeyBundleForContact(pubKey) { async function getPreKeyBundleForContact(pubKey) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const identityKey = myKeyPair.pubKey; const identityKey = myKeyPair.pubKey;
@ -117,129 +113,6 @@
} }
} }
// fetches device mappings from server.
async function getPrimaryDeviceMapping(pubKey) {
if (typeof lokiFileServerAPI === 'undefined') {
// If this is not defined then we are initiating a pairing
return [];
}
const deviceMapping = await lokiFileServerAPI.getUserDeviceMapping(pubKey);
if (!deviceMapping) {
return [];
}
let authorisations = deviceMapping.authorisations || [];
if (deviceMapping.isPrimary === '0') {
const { primaryDevicePubKey } =
authorisations.find(
authorisation =>
authorisation && authorisation.secondaryDevicePubKey === pubKey
) || {};
if (primaryDevicePubKey) {
// do NOT call getprimaryDeviceMapping recursively
// in case both devices are out of sync and think they are
// each others' secondary pubkey.
const primaryDeviceMapping = await lokiFileServerAPI.getUserDeviceMapping(
primaryDevicePubKey
);
if (!primaryDeviceMapping) {
return [];
}
({ authorisations } = primaryDeviceMapping);
}
}
// filter out any invalid authorisations
return authorisations.filter(a => a && typeof a === 'object') || [];
}
// if the device is a secondary device,
// fetch the device mappings for its primary device
async function saveAllPairingAuthorisationsFor(pubKey) {
// Will be false if there is no timer
const cacheValid = timers[pubKey] > Date.now();
if (cacheValid) {
return;
}
timers[pubKey] = Date.now() + REFRESH_DELAY;
const authorisations = await getPrimaryDeviceMapping(pubKey);
await Promise.all(
authorisations.map(authorisation =>
savePairingAuthorisation(authorisation)
)
);
}
async function savePairingAuthorisation(authorisation) {
if (!authorisation) {
return;
}
// Ensure that we have a conversation for all the devices
const conversation = await ConversationController.getOrCreateAndWait(
authorisation.secondaryDevicePubKey,
'private'
);
await conversation.setSecondaryStatus(
true,
authorisation.primaryDevicePubKey
);
await window.Signal.Data.createOrUpdatePairingAuthorisation(authorisation);
}
function removePairingAuthorisationForSecondaryPubKey(pubKey) {
return window.Signal.Data.removePairingAuthorisationForSecondaryPubKey(
pubKey
);
}
// Transforms signatures from base64 to ArrayBuffer!
async function getGrantAuthorisationForSecondaryPubKey(secondaryPubKey) {
const conversation = ConversationController.get(secondaryPubKey);
if (!conversation || conversation.isPublic() || conversation.isRss()) {
return null;
}
await saveAllPairingAuthorisationsFor(secondaryPubKey);
const authorisation = await window.Signal.Data.getGrantAuthorisationForSecondaryPubKey(
secondaryPubKey
);
if (!authorisation) {
return null;
}
return {
...authorisation,
requestSignature: Signal.Crypto.base64ToArrayBuffer(
authorisation.requestSignature
),
grantSignature: Signal.Crypto.base64ToArrayBuffer(
authorisation.grantSignature
),
};
}
// Transforms signatures from base64 to ArrayBuffer!
async function getAuthorisationForSecondaryPubKey(secondaryPubKey) {
await saveAllPairingAuthorisationsFor(secondaryPubKey);
const authorisation = await window.Signal.Data.getAuthorisationForSecondaryPubKey(
secondaryPubKey
);
if (!authorisation) {
return null;
}
return {
...authorisation,
requestSignature: Signal.Crypto.base64ToArrayBuffer(
authorisation.requestSignature
),
grantSignature: authorisation.grantSignature
? Signal.Crypto.base64ToArrayBuffer(authorisation.grantSignature)
: null,
};
}
function getSecondaryDevicesFor(primaryDevicePubKey) {
return window.Signal.Data.getSecondaryDevicesFor(primaryDevicePubKey);
}
function getGuardNodes() { function getGuardNodes() {
return window.Signal.Data.getGuardNodes(); return window.Signal.Data.getGuardNodes();
} }
@ -248,31 +121,11 @@
return window.Signal.Data.updateGuardNodes(nodes); return window.Signal.Data.updateGuardNodes(nodes);
} }
async function getAllDevicePubKeysForPrimaryPubKey(primaryDevicePubKey) {
await saveAllPairingAuthorisationsFor(primaryDevicePubKey);
const secondaryPubKeys =
(await getSecondaryDevicesFor(primaryDevicePubKey)) || [];
return secondaryPubKeys.concat(primaryDevicePubKey);
}
function getPairedDevicesFor(pubkey) {
return window.Signal.Data.getPairedDevicesFor(pubkey);
}
window.libloki.storage = { window.libloki.storage = {
getPreKeyBundleForContact, getPreKeyBundleForContact,
saveContactPreKeyBundle, saveContactPreKeyBundle,
removeContactPreKeyBundle, removeContactPreKeyBundle,
verifyFriendRequestAcceptPreKey, verifyFriendRequestAcceptPreKey,
savePairingAuthorisation,
saveAllPairingAuthorisationsFor,
removePairingAuthorisationForSecondaryPubKey,
getGrantAuthorisationForSecondaryPubKey,
getAuthorisationForSecondaryPubKey,
getPairedDevicesFor,
getAllDevicePubKeysForPrimaryPubKey,
getSecondaryDevicesFor,
getPrimaryDeviceMapping,
getGuardNodes, getGuardNodes,
updateGuardNodes, updateGuardNodes,
}; };

@ -3,6 +3,7 @@
textsecure, textsecure,
libsignal, libsignal,
libloki, libloki,
libsession,
lokiFileServerAPI, lokiFileServerAPI,
mnemonic, mnemonic,
btoa, btoa,
@ -573,9 +574,12 @@
secondaryDevicePubKey, secondaryDevicePubKey,
libloki.crypto.PairingType.GRANT libloki.crypto.PairingType.GRANT
); );
const existingAuthorisation = await libloki.storage.getAuthorisationForSecondaryPubKey( const authorisations = await libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(
secondaryDevicePubKey secondaryDevicePubKey
); );
const existingAuthorisation = authorisations.some(
pairing => pairing.secondaryDevicePubKey === secondaryDevicePubKey
);
if (!existingAuthorisation) { if (!existingAuthorisation) {
throw new Error( throw new Error(
'authoriseSecondaryDevice: request signature missing from database!' 'authoriseSecondaryDevice: request signature missing from database!'
@ -588,8 +592,11 @@
requestSignature, requestSignature,
grantSignature, grantSignature,
}; };
// Update authorisation in database with the new grant signature // Update authorisation in database with the new grant signature
await libloki.storage.savePairingAuthorisation(authorisation); await libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(
authorisation
);
// Try to upload to the file server and then send a message // Try to upload to the file server and then send a message
try { try {
@ -604,7 +611,7 @@
e && e.stack ? e.stack : e e && e.stack ? e.stack : e
); );
// File server upload failed or message sending failed, we should rollback changes // File server upload failed or message sending failed, we should rollback changes
await libloki.storage.removePairingAuthorisationForSecondaryPubKey( await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
secondaryDevicePubKey secondaryDevicePubKey
); );
await lokiFileServerAPI.updateOurDeviceMapping(); await lokiFileServerAPI.updateOurDeviceMapping();

@ -968,7 +968,9 @@ MessageReceiver.prototype.extend({
if (valid) { if (valid) {
// Pairing dialog is open and is listening // Pairing dialog is open and is listening
if (Whisper.events.isListenedTo('devicePairingRequestReceived')) { if (Whisper.events.isListenedTo('devicePairingRequestReceived')) {
await window.libloki.storage.savePairingAuthorisation(pairingRequest); await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(
pairingRequest
);
Whisper.events.trigger( Whisper.events.trigger(
'devicePairingRequestReceived', 'devicePairingRequestReceived',
pairingRequest.secondaryDevicePubKey pairingRequest.secondaryDevicePubKey
@ -1014,7 +1016,9 @@ MessageReceiver.prototype.extend({
window.storage.remove('secondaryDeviceStatus'); window.storage.remove('secondaryDeviceStatus');
window.storage.put('isSecondaryDevice', true); window.storage.put('isSecondaryDevice', true);
window.storage.put('primaryDevicePubKey', primaryDevicePubKey); window.storage.put('primaryDevicePubKey', primaryDevicePubKey);
await libloki.storage.savePairingAuthorisation(pairingAuthorisation); await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(
pairingAuthorisation
);
const primaryConversation = await ConversationController.getOrCreateAndWait( const primaryConversation = await ConversationController.getOrCreateAndWait(
primaryDevicePubKey, primaryDevicePubKey,
'private' 'private'
@ -1270,16 +1274,12 @@ MessageReceiver.prototype.extend({
async handleSyncMessage(envelope, syncMessage) { async handleSyncMessage(envelope, syncMessage) {
// We should only accept sync messages from our devices // We should only accept sync messages from our devices
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const ourPrimaryNumber = window.storage.get('primaryDevicePubKey'); const ourDevices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices(
const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( ourNumber
ourPrimaryNumber );
const validSyncSender = ourDevices.some(
device => device.key === envelope.source
); );
const ourDevices = new Set([
ourNumber,
ourPrimaryNumber,
...ourOtherDevices,
]);
const validSyncSender = ourDevices.has(envelope.source);
if (!validSyncSender) { if (!validSyncSender) {
throw new Error( throw new Error(
"Received sync message from a device we aren't paired with" "Received sync message from a device we aren't paired with"

@ -431,13 +431,15 @@ OutgoingMessage.prototype = {
const ourPubKey = textsecure.storage.user.getNumber(); const ourPubKey = textsecure.storage.user.getNumber();
const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey'); const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey');
const secondaryPubKeys = const secondaryPubKeys =
(await window.libloki.storage.getSecondaryDevicesFor(ourPubKey)) || []; (await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(
ourPubKey
)) || [];
let aliasedPubkey = devicePubKey; let aliasedPubkey = devicePubKey;
if (devicePubKey === ourPubKey) { if (devicePubKey === ourPubKey) {
aliasedPubkey = 'OUR_PUBKEY'; // should not happen aliasedPubkey = 'OUR_PUBKEY'; // should not happen
} else if (devicePubKey === ourPrimaryPubkey) { } else if (devicePubKey === ourPrimaryPubkey) {
aliasedPubkey = 'OUR_PRIMARY_PUBKEY'; aliasedPubkey = 'OUR_PRIMARY_PUBKEY';
} else if (secondaryPubKeys.includes(devicePubKey)) { } else if (secondaryPubKeys.some(device => device.key === devicePubKey)) {
aliasedPubkey = 'OUR SECONDARY PUBKEY'; aliasedPubkey = 'OUR SECONDARY PUBKEY';
} }
libloki.api.debug.logSessionMessageSending( libloki.api.debug.logSessionMessageSending(

@ -537,12 +537,12 @@ MessageSender.prototype = {
window.storage.get('primaryDevicePubKey') || window.storage.get('primaryDevicePubKey') ||
textsecure.storage.user.getNumber(); textsecure.storage.user.getNumber();
const allOurDevices = ( const allOurDevices = (
await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(
primaryDeviceKey primaryDeviceKey
) )
) )
// Don't send to ourselves // Don't send to ourselves
.filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); .filter(pubKey => pubKey.key !== textsecure.storage.user.getNumber());
if (allOurDevices.length === 0) { if (allOurDevices.length === 0) {
return null; return null;
} }

@ -7,6 +7,9 @@ import {
SessionButtonColor, SessionButtonColor,
SessionButtonType, SessionButtonType,
} from '../SessionButton'; } from '../SessionButton';
import { UserUtil } from '../../../util';
import { MultiDeviceProtocol } from '../../../session/protocols';
import { PubKey } from '../../../session/types';
export enum SessionSettingCategory { export enum SessionSettingCategory {
Appearance = 'appearance', Appearance = 'appearance',
@ -80,10 +83,10 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
window.Whisper.events.on('refreshLinkedDeviceList', async () => { window.Whisper.events.on('refreshLinkedDeviceList', async () => {
setTimeout(() => { setTimeout(() => {
this.refreshLinkedDevice(); void this.refreshLinkedDevice();
}, 1000); }, 1000);
}); });
this.refreshLinkedDevice(); void this.refreshLinkedDevice();
} }
public componentWillUnmount() { public componentWillUnmount() {
@ -644,16 +647,14 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
} }
} }
private refreshLinkedDevice() { private async refreshLinkedDevice() {
const ourPubKey = window.textsecure.storage.user.getNumber(); const ourPubKey = await UserUtil.getCurrentDevicePubKey();
if (ourPubKey) {
const pubKey = new PubKey(ourPubKey);
const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey);
window.libloki.storage this.setState({ linkedPubKeys: devices.map(d => d.key) });
.getSecondaryDevicesFor(ourPubKey) }
.then((pubKeys: any) => {
this.setState({
linkedPubKeys: pubKeys,
});
});
} }
private async onKeyUp(event: any) { private async onKeyUp(event: any) {

@ -3,6 +3,9 @@ import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionSettingCategory, SettingsViewProps } from './SessionSettings'; import { SessionSettingCategory, SettingsViewProps } from './SessionSettings';
import { SessionButton } from '../SessionButton'; import { SessionButton } from '../SessionButton';
import { UserUtil } from '../../../util';
import { PubKey } from '../../../session/types';
import { MultiDeviceProtocol } from '../../../session/protocols';
interface Props extends SettingsViewProps { interface Props extends SettingsViewProps {
// showLinkDeviceButton is used to completely hide the button while the settings password lock is displayed // showLinkDeviceButton is used to completely hide the button while the settings password lock is displayed
@ -37,22 +40,20 @@ export class SettingsHeader extends React.Component<Props, any> {
public componentDidMount() { public componentDidMount() {
if (!this.props.isSecondaryDevice) { if (!this.props.isSecondaryDevice) {
window.Whisper.events.on('refreshLinkedDeviceList', async () => { window.Whisper.events.on('refreshLinkedDeviceList', async () => {
this.refreshLinkedDevice(); void this.refreshLinkedDevice();
}); });
this.refreshLinkedDevice(); void this.refreshLinkedDevice();
} }
} }
public refreshLinkedDevice() { public async refreshLinkedDevice() {
const ourPubKey = window.textsecure.storage.user.getNumber(); const ourPubKey = await UserUtil.getCurrentDevicePubKey();
if (ourPubKey) {
const pubKey = new PubKey(ourPubKey);
const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey);
window.libloki.storage this.setState({ disableLinkDeviceButton: devices.length > 0 });
.getSecondaryDevicesFor(ourPubKey) }
.then((pubKeys: any) => {
this.setState({
disableLinkDeviceButton: pubKeys && pubKeys.length > 0,
});
});
} }
public componentWillUnmount() { public componentWillUnmount() {

@ -15,6 +15,8 @@ import { SignalService } from './../protobuf';
import { removeFromCache } from './cache'; import { removeFromCache } from './cache';
import { toNumber } from 'lodash'; import { toNumber } from 'lodash';
import { DataMessage } from '../session/messages/outgoing'; import { DataMessage } from '../session/messages/outgoing';
import { MultiDeviceProtocol } from '../session/protocols';
import { PubKey } from '../session/types';
export { handleEndSession, handleMediumGroupUpdate }; export { handleEndSession, handleMediumGroupUpdate };
@ -265,7 +267,12 @@ async function handleSecondaryDeviceFriendRequest(pubKey: string) {
if (!c || !(await c.isFriendWithAnyDevice())) { if (!c || !(await c.isFriendWithAnyDevice())) {
return false; return false;
} }
await window.libloki.storage.savePairingAuthorisation(authorisation); await MultiDeviceProtocol.savePairingAuthorisation({
primaryDevicePubKey: authorisation.primaryDevicePubKey,
secondaryDevicePubKey: authorisation.secondaryDevicePubKey,
requestSignature: Buffer.from(authorisation.requestSignature).buffer,
grantSignature: Buffer.from(authorisation.grantSignature).buffer,
});
return true; return true;
} }
@ -601,14 +608,11 @@ export async function handleDataMessage(
const source = envelope.senderIdentity || senderPubKey; const source = envelope.senderIdentity || senderPubKey;
const isOwnDevice = async (pubkey: string) => { const isOwnDevice = async (device: string) => {
const primaryDevice = window.storage.get('primaryDevicePubKey'); const pubKey = new PubKey(device);
const secondaryDevices = await window.libloki.storage.getPairedDevicesFor( const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey);
primaryDevice
);
const allDevices = [primaryDevice, ...secondaryDevices]; return allDevices.some(d => PubKey.isEqual(d, pubKey));
return allDevices.includes(pubkey);
}; };
const ownDevice = await isOwnDevice(source); const ownDevice = await isOwnDevice(source);
@ -773,13 +777,7 @@ export async function handleMessageEvent(event: any): Promise<void> {
// - group.id if it is a group message // - group.id if it is a group message
let conversationId = id; let conversationId = id;
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( const primarySource = await MultiDeviceProtocol.getPrimaryDevice(source);
source
);
const primarySource =
(authorisation && authorisation.primaryDevicePubKey) || source;
if (isGroupMessage) { if (isGroupMessage) {
/* handle one part of the group logic here: /* handle one part of the group logic here:
handle requesting info of a new group, handle requesting info of a new group,
@ -789,7 +787,7 @@ export async function handleMessageEvent(event: any): Promise<void> {
const shouldReturn = await preprocessGroupMessage( const shouldReturn = await preprocessGroupMessage(
source, source,
message.group, message.group,
primarySource primarySource.key
); );
// handleGroupMessage() can process fully a message in some cases // handleGroupMessage() can process fully a message in some cases
@ -800,9 +798,9 @@ export async function handleMessageEvent(event: any): Promise<void> {
} }
} }
if (source !== ourNumber && authorisation) { if (source !== ourNumber) {
// Ignore auth from our devices // Ignore auth from our devices
conversationId = authorisation.primaryDevicePubKey; conversationId = primarySource.key;
} }
// the conversation with the primary device of that source (can be the same as conversationOrigin) // the conversation with the primary device of that source (can be the same as conversationOrigin)

@ -1,8 +1,9 @@
import * as Messages from './messages'; import * as Messages from './messages';
import * as Protocols from './protocols'; import * as Protocols from './protocols';
import * as Types from './types';
// TODO: Do we export class instances here? // TODO: Do we export class instances here?
// E.g // E.g
// export const messageQueue = new MessageQueue() // export const messageQueue = new MessageQueue()
export { Messages, Protocols }; export { Messages, Protocols, Types };

@ -1,6 +1,222 @@
// TODO: Populate this with multi device specific code, e.g getting linked devices for a user etc... import _ from 'lodash';
// We need to deprecate the multi device code we have in js and slowly transition to this file import {
createOrUpdatePairingAuthorisation,
getPairingAuthorisationsFor,
PairingAuthorisation,
removePairingAuthorisationsFor,
} from '../../../js/modules/data';
import { PrimaryPubKey, PubKey, SecondaryPubKey } from '../types';
import { UserUtil } from '../../util';
export function implementStuffHere() { /*
throw new Error("Don't call me :("); The reason we're exporing a class here instead of just exporting the functions directly is for the sake of testing.
We might want to stub out specific functions inside the multi device protocol itself but when exporting functions directly then it's not possible without weird hacks.
*/
// tslint:disable-next-line: no-unnecessary-class
export class MultiDeviceProtocol {
public static refreshDelay: number = 5 * 60 * 1000; // 5 minutes
private static lastFetch: { [device: string]: number } = {};
/**
* Fetch pairing authorisations from the file server if needed and store it in the database.
*
* This will fetch authorisations if:
* - It is not one of our device
* - The time since last fetch is more than refresh delay
*/
public static async fetchPairingAuthorisationsIfNeeded(
device: PubKey
): Promise<void> {
// This return here stops an infinite loop when we get all our other devices
const ourKey = await UserUtil.getCurrentDevicePubKey();
if (!ourKey || device.key === ourKey) {
return;
}
// We always prefer our local pairing over the one on the server
const ourDevices = await this.getAllDevices(ourKey);
if (ourDevices.some(d => d.key === device.key)) {
return;
}
// Only fetch if we hit the refresh delay
const lastFetchTime = this.lastFetch[device.key];
if (lastFetchTime && lastFetchTime + this.refreshDelay < Date.now()) {
return;
}
this.lastFetch[device.key] = Date.now();
try {
const authorisations = await this.fetchPairingAuthorisations(device);
// TODO: validate?
await Promise.all(authorisations.map(this.savePairingAuthorisation));
} catch (e) {
// Something went wrong, let it re-try another time
this.lastFetch[device.key] = lastFetchTime;
}
}
/**
* Reset the pairing fetched cache.
*
* This will make it so the next call to `fetchPairingAuthorisationsIfNeeded` will fetch mappings from the server.
*/
public static resetFetchCache() {
this.lastFetch = {};
}
/**
* Fetch pairing authorisations for the given device from the file server.
* This function will not save the authorisations to the database.
*
* @param device The device to fetch the authorisation for.
*/
public static async fetchPairingAuthorisations(
device: PubKey
): Promise<Array<PairingAuthorisation>> {
if (!window.lokiFileServerAPI) {
throw new Error('lokiFileServerAPI is not initialised.');
}
const mapping = await window.lokiFileServerAPI.getUserDeviceMapping(
device.key
);
// TODO: Filter out invalid authorisations
return mapping.authorisations.map(
({
primaryDevicePubKey,
secondaryDevicePubKey,
requestSignature,
grantSignature,
}) => ({
primaryDevicePubKey,
secondaryDevicePubKey,
requestSignature: Buffer.from(requestSignature, 'base64').buffer,
grantSignature: Buffer.from(grantSignature, 'base64').buffer,
})
);
}
/**
* Save pairing authorisation to the database.
* @param authorisation The pairing authorisation.
*/
public static async savePairingAuthorisation(
authorisation: PairingAuthorisation
): Promise<void> {
return createOrUpdatePairingAuthorisation(authorisation);
}
/**
* Get pairing authorisations for a given device.
* @param device The device to get pairing authorisations for.
*/
public static async getPairingAuthorisations(
device: PubKey | string
): Promise<Array<PairingAuthorisation>> {
const pubKey = typeof device === 'string' ? new PubKey(device) : device;
await this.fetchPairingAuthorisationsIfNeeded(pubKey);
return getPairingAuthorisationsFor(pubKey.key);
}
/**
* Remove all pairing authorisations for a given device.
* @param device The device to remove authorisation for.
*/
public static async removePairingAuthorisations(
device: PubKey | string
): Promise<void> {
const pubKey = typeof device === 'string' ? new PubKey(device) : device;
return removePairingAuthorisationsFor(pubKey.key);
}
/**
* Get all devices linked to a user.
*
* @param user The user to get all the devices from.
*/
public static async getAllDevices(
user: PubKey | string
): Promise<Array<PubKey>> {
const pubKey = typeof user === 'string' ? new PubKey(user) : user;
const authorisations = await this.getPairingAuthorisations(pubKey);
const devices = _.flatMap(
authorisations,
({ primaryDevicePubKey, secondaryDevicePubKey }) => [
primaryDevicePubKey,
secondaryDevicePubKey,
]
);
return _.uniq(devices).map(p => new PubKey(p));
}
/**
* Get the primary device linked to a user.
*
* @param user The user to get primary device for.
*/
public static async getPrimaryDevice(
user: PubKey | string
): Promise<PrimaryPubKey> {
const pubKey = typeof user === 'string' ? new PubKey(user) : user;
const authorisations = await this.getPairingAuthorisations(pubKey);
if (authorisations.length === 0) {
return pubKey;
}
const primary = PrimaryPubKey.from(authorisations[0].primaryDevicePubKey);
if (!primary) {
throw new Error(`Primary user public key is invalid for ${pubKey.key}.`);
}
return primary;
}
/**
* Get all the secondary devices linked to a user.
*
* @param user The user to get the devices from.
*/
public static async getSecondaryDevices(
user: PubKey | string
): Promise<Array<SecondaryPubKey>> {
const primary = await this.getPrimaryDevice(user);
const authorisations = await this.getPairingAuthorisations(primary);
return authorisations
.map(a => a.secondaryDevicePubKey)
.map(pubKey => new SecondaryPubKey(pubKey));
}
/**
* Get all devices linked to the current user.
*/
public static async getOurDevices(): Promise<Array<PubKey>> {
const ourPubKey = await UserUtil.getCurrentDevicePubKey();
if (!ourPubKey) {
throw new Error('Public key not set.');
}
return this.getAllDevices(ourPubKey);
}
/**
* Check if the given device is one of our own.
* @param device The device to check.
*/
public static async isOurDevice(device: PubKey | string): Promise<boolean> {
const pubKey = typeof device === 'string' ? new PubKey(device) : device;
try {
const ourDevices = await this.getOurDevices();
return ourDevices.some(d => PubKey.isEqual(d, pubKey));
} catch (e) {
return false;
}
}
} }

@ -1,4 +1,4 @@
import { SessionProtocol } from './SessionProtocol'; import { SessionProtocol } from './SessionProtocol';
import * as MultiDeviceProtocol from './MultiDeviceProtocol'; export * from './MultiDeviceProtocol';
export { SessionProtocol, MultiDeviceProtocol }; export { SessionProtocol };

@ -1,5 +1,3 @@
import { getPairedDevicesFor } from '../../../js/modules/data';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { import {
MessageQueueInterface, MessageQueueInterface,
@ -20,7 +18,7 @@ import {
} from '../utils'; } from '../utils';
import { PubKey } from '../types'; import { PubKey } from '../types';
import { MessageSender } from '.'; import { MessageSender } from '.';
import { SessionProtocol } from '../protocols'; import { MultiDeviceProtocol, SessionProtocol } from '../protocols';
import { UserUtil } from '../../util'; import { UserUtil } from '../../util';
export class MessageQueue implements MessageQueueInterface { export class MessageQueue implements MessageQueueInterface {
@ -35,8 +33,7 @@ export class MessageQueue implements MessageQueueInterface {
} }
public async sendUsingMultiDevice(user: PubKey, message: ContentMessage) { public async sendUsingMultiDevice(user: PubKey, message: ContentMessage) {
const userLinked = await getPairedDevicesFor(user.key); const userDevices = await MultiDeviceProtocol.getAllDevices(user.key);
const userDevices = userLinked.map(d => new PubKey(d));
await this.sendMessageToDevices(userDevices, message); await this.sendMessageToDevices(userDevices, message);
} }
@ -56,11 +53,10 @@ export class MessageQueue implements MessageQueueInterface {
const currentDevice = await UserUtil.getCurrentDevicePubKey(); const currentDevice = await UserUtil.getCurrentDevicePubKey();
if (currentDevice) { if (currentDevice) {
const otherDevices = await getPairedDevicesFor(currentDevice); const ourDevices = await MultiDeviceProtocol.getAllDevices(
currentDevice
const ourDevices = [currentDevice, ...otherDevices].map(
device => new PubKey(device)
); );
await this.sendSyncMessage(message, ourDevices); await this.sendSyncMessage(message, ourDevices);
// Remove our devices from currentDevices // Remove our devices from currentDevices

@ -31,3 +31,6 @@ export class PubKey {
return key.key === comparator.key; return key.key === comparator.key;
} }
} }
export class PrimaryPubKey extends PubKey {}
export class SecondaryPubKey extends PubKey {}

@ -1,11 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as UserUtils from '../../util/user'; import * as UserUtils from '../../util/user';
import { import { getAllConversations } from '../../../js/modules/data';
getAllConversations,
getPrimaryDeviceFor,
} from '../../../js/modules/data';
import { ContentMessage, SyncMessage } from '../messages/outgoing'; import { ContentMessage, SyncMessage } from '../messages/outgoing';
import { MultiDeviceProtocol } from '../protocols';
export function from(message: ContentMessage): SyncMessage | undefined { export function from(message: ContentMessage): SyncMessage | undefined {
// const { timestamp, identifier } = message; // const { timestamp, identifier } = message;
@ -29,7 +26,7 @@ export async function getSyncContacts(): Promise<Array<any> | undefined> {
return []; return [];
} }
const primaryDevice = await getPrimaryDeviceFor(thisDevice); const primaryDevice = await MultiDeviceProtocol.getPrimaryDevice(thisDevice);
const conversations = await getAllConversations({ const conversations = await getAllConversations({
ConversationCollection: window.Whisper.ConversationCollection, ConversationCollection: window.Whisper.ConversationCollection,
}); });
@ -61,7 +58,7 @@ export async function getSyncContacts(): Promise<Array<any> | undefined> {
const secondaryContacts = (await Promise.all(seondaryContactsPromise)) const secondaryContacts = (await Promise.all(seondaryContactsPromise))
// Filter out our primary key if it was added here // Filter out our primary key if it was added here
.filter(c => c.id !== primaryDevice); .filter(c => c.id !== primaryDevice.key);
// Return unique contacts // Return unique contacts
return _.uniqBy( return _.uniqBy(

@ -5,11 +5,7 @@ import { AdvancedSearchOptions, SearchOptions } from '../../types/Search';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { getMessageModel } from '../../shims/Whisper'; import { getMessageModel } from '../../shims/Whisper';
import { cleanSearchTerm } from '../../util/cleanSearchTerm'; import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import { import { searchConversations, searchMessages } from '../../../js/modules/data';
getPrimaryDeviceFor,
searchConversations,
searchMessages,
} from '../../../js/modules/data';
import { makeLookup } from '../../util/makeLookup'; import { makeLookup } from '../../util/makeLookup';
import { import {
@ -19,6 +15,8 @@ import {
RemoveAllConversationsActionType, RemoveAllConversationsActionType,
SelectedConversationChangedActionType, SelectedConversationChangedActionType,
} from './conversations'; } from './conversations';
import { MultiDeviceProtocol } from '../../session/protocols';
import { PubKey } from '../../session/types';
// State // State
@ -283,15 +281,13 @@ async function queryConversationsAndContacts(
query query
); );
const ourPrimaryDevice = isSecondaryDevice const ourPrimaryDevice = await MultiDeviceProtocol.getPrimaryDevice(
? await getPrimaryDeviceFor(ourNumber) ourNumber
: ourNumber; );
const resultPrimaryDevices: Array<string | null> = await Promise.all( const resultPrimaryDevices = await Promise.all(
searchResults.map(async conversation => searchResults.map(async conversation =>
conversation.id === ourPrimaryDevice MultiDeviceProtocol.getPrimaryDevice(conversation.id)
? Promise.resolve(ourPrimaryDevice)
: getPrimaryDeviceFor(conversation.id)
) )
); );
@ -304,10 +300,13 @@ async function queryConversationsAndContacts(
const primaryDevice = resultPrimaryDevices[i]; const primaryDevice = resultPrimaryDevices[i];
if (primaryDevice) { if (primaryDevice) {
if (isSecondaryDevice && primaryDevice === ourPrimaryDevice) { if (
isSecondaryDevice &&
PubKey.isEqual(primaryDevice, ourPrimaryDevice)
) {
conversations.push(ourNumber); conversations.push(ourNumber);
} else { } else {
conversations.push(primaryDevice); conversations.push(primaryDevice.key);
} }
} else if (conversation.type === 'direct') { } else if (conversation.type === 'direct') {
contacts.push(conversation.id); contacts.push(conversation.id);

2
ts/window.d.ts vendored

@ -46,7 +46,7 @@ declare global {
libsignal: LibsignalProtocol; libsignal: LibsignalProtocol;
log: any; log: any;
lokiFeatureFlags: any; lokiFeatureFlags: any;
lokiFileServerAPI: any; lokiFileServerAPI: LokiFileServerInstance;
lokiMessageAPI: LokiMessageAPI; lokiMessageAPI: LokiMessageAPI;
lokiPublicChatAPI: LokiPublicChatFactoryAPI; lokiPublicChatAPI: LokiPublicChatFactoryAPI;
mnemonic: any; mnemonic: any;

Loading…
Cancel
Save