mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Merge pull request #1176 from Mikunj/multi-device-protocol
Multidevice Protocol Refactor
This commit is contained in:
commit
ce868456c2
99
app/sql.js
99
app/sql.js
|
@ -74,12 +74,8 @@ module.exports = {
|
|||
removeAllContactSignedPreKeys,
|
||||
|
||||
createOrUpdatePairingAuthorisation,
|
||||
removePairingAuthorisationForSecondaryPubKey,
|
||||
getAuthorisationForSecondaryPubKey,
|
||||
getGrantAuthorisationsForPrimaryPubKey,
|
||||
getSecondaryDevicesFor,
|
||||
getPrimaryDeviceFor,
|
||||
getPairedDevicesFor,
|
||||
getPairingAuthorisationsFor,
|
||||
removePairingAuthorisationsFor,
|
||||
|
||||
createOrUpdateItem,
|
||||
getItemById,
|
||||
|
@ -1506,42 +1502,19 @@ async function removeAllSignedPreKeys() {
|
|||
|
||||
const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations';
|
||||
|
||||
const GUARD_NODE_TABLE = 'guardNodes';
|
||||
|
||||
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) {
|
||||
async function getPairingAuthorisationsFor(pubKey) {
|
||||
const rows = await db.all(
|
||||
`SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey AND isGranted = 1 ORDER BY secondaryDevicePubKey ASC;`,
|
||||
{
|
||||
$primaryDevicePubKey: primaryDevicePubKey,
|
||||
}
|
||||
`SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`,
|
||||
{ $pubKey: pubKey }
|
||||
);
|
||||
return map(rows, row => jsonToObject(row.json));
|
||||
|
||||
return rows.map(row => jsonToObject(row.json));
|
||||
}
|
||||
|
||||
async function createOrUpdatePairingAuthorisation(data) {
|
||||
const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data;
|
||||
// remove any existing authorisation for this pubkey (we allow only one secondary device for now)
|
||||
await removePairingAuthorisationForPrimaryPubKey(primaryDevicePubKey);
|
||||
await removePairingAuthorisationsFor(primaryDevicePubKey);
|
||||
|
||||
await db.run(
|
||||
`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;`,
|
||||
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $pubKey OR secondaryDevicePubKey = $pubKey;`,
|
||||
{
|
||||
$primaryDevicePubKey: pubKey,
|
||||
$pubKey: pubKey,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function removePairingAuthorisationForSecondaryPubKey(pubKey) {
|
||||
await db.run(
|
||||
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey;`,
|
||||
{
|
||||
$secondaryDevicePubKey: pubKey,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getSecondaryDevicesFor(primaryDevicePubKey) {
|
||||
const authorisations = await getGrantAuthorisationsForPrimaryPubKey(
|
||||
primaryDevicePubKey
|
||||
);
|
||||
return map(authorisations, row => row.secondaryDevicePubKey);
|
||||
}
|
||||
const GUARD_NODE_TABLE = 'guardNodes';
|
||||
|
||||
async function getGuardNodes() {
|
||||
const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`);
|
||||
|
@ -1620,42 +1579,8 @@ async function updateGuardNodes(nodes) {
|
|||
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),
|
||||
// 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';
|
||||
async function createOrUpdateItem(data) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
textsecure,
|
||||
Whisper,
|
||||
libloki,
|
||||
libsession,
|
||||
libsignal,
|
||||
StringView,
|
||||
BlockedNumberController,
|
||||
|
@ -1425,7 +1426,7 @@
|
|||
|
||||
Whisper.events.on('devicePairingRequestRejected', async pubKey => {
|
||||
await libloki.storage.removeContactPreKeyBundle(pubKey);
|
||||
await libloki.storage.removePairingAuthorisationForSecondaryPubKey(
|
||||
await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
|
||||
pubKey
|
||||
);
|
||||
});
|
||||
|
@ -1435,8 +1436,7 @@
|
|||
if (isSecondaryDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
await libloki.storage.removePairingAuthorisationForSecondaryPubKey(
|
||||
await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
|
||||
pubKey
|
||||
);
|
||||
await window.lokiFileServerAPI.updateOurDeviceMapping();
|
||||
|
@ -1761,16 +1761,15 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let primaryDevice = null;
|
||||
const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey(
|
||||
sender
|
||||
);
|
||||
if (authorisation) {
|
||||
primaryDevice = authorisation.primaryDevicePubKey;
|
||||
}
|
||||
// A sender here could be referring to a group.
|
||||
// Groups don't have primary devices so we need to take that into consideration.
|
||||
const user = libsession.Types.PubKey.from(sender);
|
||||
const primaryDevice = user
|
||||
? await libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(user)
|
||||
: null;
|
||||
|
||||
const conversation = ConversationController.get(
|
||||
groupId || primaryDevice || sender
|
||||
groupId || (primaryDevice && primaryDevice.key) || sender
|
||||
);
|
||||
|
||||
if (conversation) {
|
||||
|
@ -1826,25 +1825,21 @@
|
|||
activeAt = activeAt || Date.now();
|
||||
}
|
||||
const ourPrimaryKey = window.storage.get('primaryDevicePubKey');
|
||||
const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
|
||||
ourPrimaryKey
|
||||
);
|
||||
// TODO: We should probably just *not* send any secondary devices and
|
||||
// just load them all and send FRs when we get the mapping
|
||||
const isOurSecondaryDevice =
|
||||
id !== ourPrimaryKey &&
|
||||
ourDevices &&
|
||||
ourDevices.some(devicePubKey => devicePubKey === id);
|
||||
|
||||
if (isOurSecondaryDevice) {
|
||||
await conversation.setSecondaryStatus(true, ourPrimaryKey);
|
||||
if (ourPrimaryKey) {
|
||||
const secondaryDevices = await libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(
|
||||
ourPrimaryKey
|
||||
);
|
||||
if (secondaryDevices.some(device => device.key === id)) {
|
||||
await conversation.setSecondaryStatus(true, ourPrimaryKey);
|
||||
}
|
||||
}
|
||||
|
||||
const otherDevices = await libloki.storage.getPairedDevicesFor(id);
|
||||
const devices = [id, ...otherDevices];
|
||||
const devices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices(
|
||||
id
|
||||
);
|
||||
const deviceConversations = await Promise.all(
|
||||
devices.map(d =>
|
||||
ConversationController.getOrCreateAndWait(d, 'private')
|
||||
ConversationController.getOrCreateAndWait(d.key, 'private')
|
||||
)
|
||||
);
|
||||
deviceConversations.forEach(device => {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
ConversationController,
|
||||
MessageController,
|
||||
_,
|
||||
libloki,
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -31,18 +30,15 @@
|
|||
this.remove(receipts);
|
||||
return receipts;
|
||||
},
|
||||
async getTargetMessage(source, messages) {
|
||||
async getTargetMessage(originalSource, messages) {
|
||||
if (messages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey(
|
||||
source
|
||||
const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(
|
||||
originalSource
|
||||
);
|
||||
if (authorisation) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
source = authorisation.primaryDevicePubKey;
|
||||
}
|
||||
const source = primary.key;
|
||||
|
||||
const message = messages.find(
|
||||
item => !item.isIncoming() && source === item.get('conversationId')
|
||||
|
|
|
@ -211,10 +211,9 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
const ourDevices = await window.libloki.storage.getPairedDevicesFor(
|
||||
this.ourNumber
|
||||
return window.libsession.Protocols.MultiDeviceProtocol.isOurDevice(
|
||||
this.id
|
||||
);
|
||||
return ourDevices.includes(this.id);
|
||||
},
|
||||
isOurLocalDevice() {
|
||||
return this.id === this.ourNumber;
|
||||
|
@ -876,15 +875,19 @@
|
|||
// This is already the primary conversation
|
||||
return this;
|
||||
}
|
||||
const authorisation = await window.libloki.storage.getAuthorisationForSecondaryPubKey(
|
||||
this.id
|
||||
);
|
||||
if (authorisation) {
|
||||
|
||||
const device = window.libsession.Types.PubKey.from(this.id);
|
||||
if (device) {
|
||||
const primary = await window.libsession.Protocols.MultiDeviceProtocol.getPrimaryDevice(
|
||||
device
|
||||
);
|
||||
|
||||
return ConversationController.getOrCreateAndWait(
|
||||
authorisation.primaryDevicePubKey,
|
||||
primary.key,
|
||||
'private'
|
||||
);
|
||||
}
|
||||
|
||||
// Something funky has happened
|
||||
return this;
|
||||
},
|
||||
|
|
1
js/models/message.d.ts
vendored
1
js/models/message.d.ts
vendored
|
@ -1,4 +1,3 @@
|
|||
|
||||
type MessageModelType = 'incoming' | 'outgoing' | 'friend-request';
|
||||
export type EndSessionType = 'done' | 'ongoing';
|
||||
|
||||
|
|
21
js/modules/data.d.ts
vendored
21
js/modules/data.d.ts
vendored
|
@ -46,7 +46,7 @@ type PairingAuthorisation = {
|
|||
primaryDevicePubKey: string;
|
||||
secondaryDevicePubKey: string;
|
||||
requestSignature: ArrayBuffer;
|
||||
grantSignature: ArrayBuffer | null;
|
||||
grantSignature?: ArrayBuffer;
|
||||
};
|
||||
|
||||
type GuardNode = {
|
||||
|
@ -153,25 +153,10 @@ export function removeAllContactSignedPreKeys(): Promise<void>;
|
|||
export function createOrUpdatePairingAuthorisation(
|
||||
data: PairingAuthorisation
|
||||
): Promise<void>;
|
||||
export function removePairingAuthorisationForSecondaryPubKey(
|
||||
pubKey: string
|
||||
): Promise<void>;
|
||||
export function getGrantAuthorisationsForPrimaryPubKey(
|
||||
export function getPairingAuthorisationsFor(
|
||||
pubKey: string
|
||||
): Promise<Array<PairingAuthorisation>>;
|
||||
export function getGrantAuthorisationForSecondaryPubKey(
|
||||
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>>;
|
||||
export function removePairingAuthorisationsFor(pubKey: string): Promise<void>;
|
||||
|
||||
// Guard Nodes
|
||||
export function getGuardNodes(): Promise<GuardNode>;
|
||||
|
|
|
@ -89,13 +89,8 @@ module.exports = {
|
|||
removeAllContactSignedPreKeys,
|
||||
|
||||
createOrUpdatePairingAuthorisation,
|
||||
removePairingAuthorisationForSecondaryPubKey,
|
||||
getGrantAuthorisationForSecondaryPubKey,
|
||||
getAuthorisationForSecondaryPubKey,
|
||||
getGrantAuthorisationsForPrimaryPubKey,
|
||||
getSecondaryDevicesFor,
|
||||
getPrimaryDeviceFor,
|
||||
getPairedDevicesFor,
|
||||
getPairingAuthorisationsFor,
|
||||
removePairingAuthorisationsFor,
|
||||
|
||||
getGuardNodes,
|
||||
updateGuardNodes,
|
||||
|
@ -631,29 +626,20 @@ async function createOrUpdatePairingAuthorisation(data) {
|
|||
});
|
||||
}
|
||||
|
||||
async function removePairingAuthorisationForSecondaryPubKey(pubKey) {
|
||||
if (!pubKey) {
|
||||
return;
|
||||
}
|
||||
await channels.removePairingAuthorisationForSecondaryPubKey(pubKey);
|
||||
async function getPairingAuthorisationsFor(pubKey) {
|
||||
const authorisations = channels.getPairingAuthorisationsFor(pubKey);
|
||||
|
||||
return authorisations.map(authorisation => ({
|
||||
...authorisation,
|
||||
requestSignature: base64ToArrayBuffer(authorisation.requestSignature),
|
||||
grantSignature: authorisation.grantSignature
|
||||
? base64ToArrayBuffer(authorisation.grantSignature)
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
async function getGrantAuthorisationForSecondaryPubKey(pubKey) {
|
||||
return channels.getAuthorisationForSecondaryPubKey(pubKey, {
|
||||
granted: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getGrantAuthorisationsForPrimaryPubKey(pubKey) {
|
||||
return channels.getGrantAuthorisationsForPrimaryPubKey(pubKey);
|
||||
}
|
||||
|
||||
function getAuthorisationForSecondaryPubKey(pubKey) {
|
||||
return channels.getAuthorisationForSecondaryPubKey(pubKey);
|
||||
}
|
||||
|
||||
function getSecondaryDevicesFor(primaryDevicePubKey) {
|
||||
return channels.getSecondaryDevicesFor(primaryDevicePubKey);
|
||||
async function removePairingAuthorisationsFor(pubKey) {
|
||||
await channels.removePairingAuthorisationsFor(pubKey);
|
||||
}
|
||||
|
||||
function getGuardNodes() {
|
||||
|
@ -664,14 +650,6 @@ function updateGuardNodes(nodes) {
|
|||
return channels.updateGuardNodes(nodes);
|
||||
}
|
||||
|
||||
function getPrimaryDeviceFor(secondaryDevicePubKey) {
|
||||
return channels.getPrimaryDeviceFor(secondaryDevicePubKey);
|
||||
}
|
||||
|
||||
function getPairedDevicesFor(pubKey) {
|
||||
return channels.getPairedDevicesFor(pubKey);
|
||||
}
|
||||
|
||||
// Items
|
||||
|
||||
const ITEM_KEYS = {
|
||||
|
|
16
js/modules/loki_file_server_api.d.ts
vendored
Normal file
16
js/modules/loki_file_server_api.d.ts
vendored
Normal file
|
@ -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 storage: false */
|
||||
/* global Signal: false */
|
||||
/* global log: false */
|
||||
|
||||
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
|
||||
|
@ -220,16 +219,10 @@ class LokiHomeServerInstance extends LokiFileServerInstance {
|
|||
|
||||
async updateOurDeviceMapping() {
|
||||
const isPrimary = !storage.get('isSecondaryDevice');
|
||||
let authorisations;
|
||||
if (isPrimary) {
|
||||
authorisations = await Signal.Data.getGrantAuthorisationsForPrimaryPubKey(
|
||||
this.ourKey
|
||||
);
|
||||
} else {
|
||||
authorisations = [
|
||||
await Signal.Data.getGrantAuthorisationForSecondaryPubKey(this.ourKey),
|
||||
];
|
||||
}
|
||||
const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(
|
||||
this.ourKey
|
||||
);
|
||||
|
||||
return this._setOurDeviceMapping(authorisations, isPrimary);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global Whisper, i18n, textsecure, libloki, _ */
|
||||
/* global Whisper, i18n, textsecure, _ */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
|
@ -197,7 +197,7 @@
|
|||
// exists in group, but hasn't yet synced with its other devices.
|
||||
const getDevicesForRemoved = async () => {
|
||||
const promises = notPresentInNew.map(member =>
|
||||
libloki.storage.getPairedDevicesFor(member)
|
||||
window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(member)
|
||||
);
|
||||
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) {
|
||||
// This message shouldn't be routed through multi-device.
|
||||
// It needs to go directly to the pubKey specified.
|
||||
|
@ -330,7 +321,6 @@
|
|||
createContactSyncProtoMessage,
|
||||
createGroupSyncProtoMessage,
|
||||
createOpenGroupsSyncProtoMessage,
|
||||
getPrimaryDevicePubkey,
|
||||
debug,
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
/* global window, libsignal, textsecure, Signal,
|
||||
lokiFileServerAPI, ConversationController */
|
||||
/* global window, libsignal, textsecure */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.libloki = window.libloki || {};
|
||||
|
||||
const timers = {};
|
||||
const REFRESH_DELAY = 60 * 1000;
|
||||
|
||||
async function getPreKeyBundleForContact(pubKey) {
|
||||
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
|
||||
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() {
|
||||
return window.Signal.Data.getGuardNodes();
|
||||
}
|
||||
|
@ -248,31 +121,11 @@
|
|||
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 = {
|
||||
getPreKeyBundleForContact,
|
||||
saveContactPreKeyBundle,
|
||||
removeContactPreKeyBundle,
|
||||
verifyFriendRequestAcceptPreKey,
|
||||
savePairingAuthorisation,
|
||||
saveAllPairingAuthorisationsFor,
|
||||
removePairingAuthorisationForSecondaryPubKey,
|
||||
getGrantAuthorisationForSecondaryPubKey,
|
||||
getAuthorisationForSecondaryPubKey,
|
||||
getPairedDevicesFor,
|
||||
getAllDevicePubKeysForPrimaryPubKey,
|
||||
getSecondaryDevicesFor,
|
||||
getPrimaryDeviceMapping,
|
||||
getGuardNodes,
|
||||
updateGuardNodes,
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
textsecure,
|
||||
libsignal,
|
||||
libloki,
|
||||
libsession,
|
||||
lokiFileServerAPI,
|
||||
mnemonic,
|
||||
btoa,
|
||||
|
@ -573,9 +574,12 @@
|
|||
secondaryDevicePubKey,
|
||||
libloki.crypto.PairingType.GRANT
|
||||
);
|
||||
const existingAuthorisation = await libloki.storage.getAuthorisationForSecondaryPubKey(
|
||||
const authorisations = await libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(
|
||||
secondaryDevicePubKey
|
||||
);
|
||||
const existingAuthorisation = authorisations.some(
|
||||
pairing => pairing.secondaryDevicePubKey === secondaryDevicePubKey
|
||||
);
|
||||
if (!existingAuthorisation) {
|
||||
throw new Error(
|
||||
'authoriseSecondaryDevice: request signature missing from database!'
|
||||
|
@ -588,8 +592,11 @@
|
|||
requestSignature,
|
||||
grantSignature,
|
||||
};
|
||||
|
||||
// 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 {
|
||||
|
@ -604,7 +611,7 @@
|
|||
e && e.stack ? e.stack : e
|
||||
);
|
||||
// File server upload failed or message sending failed, we should rollback changes
|
||||
await libloki.storage.removePairingAuthorisationForSecondaryPubKey(
|
||||
await libsession.Protocols.MultiDeviceProtocol.removePairingAuthorisations(
|
||||
secondaryDevicePubKey
|
||||
);
|
||||
await lokiFileServerAPI.updateOurDeviceMapping();
|
||||
|
|
|
@ -968,7 +968,9 @@ MessageReceiver.prototype.extend({
|
|||
if (valid) {
|
||||
// Pairing dialog is open and is listening
|
||||
if (Whisper.events.isListenedTo('devicePairingRequestReceived')) {
|
||||
await window.libloki.storage.savePairingAuthorisation(pairingRequest);
|
||||
await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(
|
||||
pairingRequest
|
||||
);
|
||||
Whisper.events.trigger(
|
||||
'devicePairingRequestReceived',
|
||||
pairingRequest.secondaryDevicePubKey
|
||||
|
@ -1014,7 +1016,9 @@ MessageReceiver.prototype.extend({
|
|||
window.storage.remove('secondaryDeviceStatus');
|
||||
window.storage.put('isSecondaryDevice', true);
|
||||
window.storage.put('primaryDevicePubKey', primaryDevicePubKey);
|
||||
await libloki.storage.savePairingAuthorisation(pairingAuthorisation);
|
||||
await window.libsession.Protocols.MultiDeviceProtocol.savePairingAuthorisation(
|
||||
pairingAuthorisation
|
||||
);
|
||||
const primaryConversation = await ConversationController.getOrCreateAndWait(
|
||||
primaryDevicePubKey,
|
||||
'private'
|
||||
|
@ -1270,16 +1274,12 @@ MessageReceiver.prototype.extend({
|
|||
async handleSyncMessage(envelope, syncMessage) {
|
||||
// We should only accept sync messages from our devices
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const ourPrimaryNumber = window.storage.get('primaryDevicePubKey');
|
||||
const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
|
||||
ourPrimaryNumber
|
||||
const ourDevices = await libsession.Protocols.MultiDeviceProtocol.getAllDevices(
|
||||
ourNumber
|
||||
);
|
||||
const validSyncSender = ourDevices.some(
|
||||
device => device.key === envelope.source
|
||||
);
|
||||
const ourDevices = new Set([
|
||||
ourNumber,
|
||||
ourPrimaryNumber,
|
||||
...ourOtherDevices,
|
||||
]);
|
||||
const validSyncSender = ourDevices.has(envelope.source);
|
||||
if (!validSyncSender) {
|
||||
throw new Error(
|
||||
"Received sync message from a device we aren't paired with"
|
||||
|
|
|
@ -431,13 +431,15 @@ OutgoingMessage.prototype = {
|
|||
const ourPubKey = textsecure.storage.user.getNumber();
|
||||
const ourPrimaryPubkey = window.storage.get('primaryDevicePubKey');
|
||||
const secondaryPubKeys =
|
||||
(await window.libloki.storage.getSecondaryDevicesFor(ourPubKey)) || [];
|
||||
(await window.libsession.Protocols.MultiDeviceProtocol.getSecondaryDevices(
|
||||
ourPubKey
|
||||
)) || [];
|
||||
let aliasedPubkey = devicePubKey;
|
||||
if (devicePubKey === ourPubKey) {
|
||||
aliasedPubkey = 'OUR_PUBKEY'; // should not happen
|
||||
} else if (devicePubKey === ourPrimaryPubkey) {
|
||||
aliasedPubkey = 'OUR_PRIMARY_PUBKEY';
|
||||
} else if (secondaryPubKeys.includes(devicePubKey)) {
|
||||
} else if (secondaryPubKeys.some(device => device.key === devicePubKey)) {
|
||||
aliasedPubkey = 'OUR SECONDARY PUBKEY';
|
||||
}
|
||||
libloki.api.debug.logSessionMessageSending(
|
||||
|
|
|
@ -537,12 +537,12 @@ MessageSender.prototype = {
|
|||
window.storage.get('primaryDevicePubKey') ||
|
||||
textsecure.storage.user.getNumber();
|
||||
const allOurDevices = (
|
||||
await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
|
||||
await window.libsession.Protocols.MultiDeviceProtocol.getAllDevices(
|
||||
primaryDeviceKey
|
||||
)
|
||||
)
|
||||
// Don't send to ourselves
|
||||
.filter(pubKey => pubKey !== textsecure.storage.user.getNumber());
|
||||
.filter(pubKey => pubKey.key !== textsecure.storage.user.getNumber());
|
||||
if (allOurDevices.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ import {
|
|||
SessionButtonColor,
|
||||
SessionButtonType,
|
||||
} from '../SessionButton';
|
||||
import { UserUtil } from '../../../util';
|
||||
import { MultiDeviceProtocol } from '../../../session/protocols';
|
||||
import { PubKey } from '../../../session/types';
|
||||
|
||||
export enum SessionSettingCategory {
|
||||
Appearance = 'appearance',
|
||||
|
@ -80,10 +83,10 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
|
|||
|
||||
window.Whisper.events.on('refreshLinkedDeviceList', async () => {
|
||||
setTimeout(() => {
|
||||
this.refreshLinkedDevice();
|
||||
void this.refreshLinkedDevice();
|
||||
}, 1000);
|
||||
});
|
||||
this.refreshLinkedDevice();
|
||||
void this.refreshLinkedDevice();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
@ -644,16 +647,14 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private refreshLinkedDevice() {
|
||||
const ourPubKey = window.textsecure.storage.user.getNumber();
|
||||
private async refreshLinkedDevice() {
|
||||
const ourPubKey = await UserUtil.getCurrentDevicePubKey();
|
||||
if (ourPubKey) {
|
||||
const pubKey = new PubKey(ourPubKey);
|
||||
const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey);
|
||||
|
||||
window.libloki.storage
|
||||
.getSecondaryDevicesFor(ourPubKey)
|
||||
.then((pubKeys: any) => {
|
||||
this.setState({
|
||||
linkedPubKeys: pubKeys,
|
||||
});
|
||||
});
|
||||
this.setState({ linkedPubKeys: devices.map(d => d.key) });
|
||||
}
|
||||
}
|
||||
|
||||
private async onKeyUp(event: any) {
|
||||
|
|
|
@ -3,6 +3,9 @@ import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
|
|||
|
||||
import { SessionSettingCategory, SettingsViewProps } from './SessionSettings';
|
||||
import { SessionButton } from '../SessionButton';
|
||||
import { UserUtil } from '../../../util';
|
||||
import { PubKey } from '../../../session/types';
|
||||
import { MultiDeviceProtocol } from '../../../session/protocols';
|
||||
|
||||
interface Props extends SettingsViewProps {
|
||||
// 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() {
|
||||
if (!this.props.isSecondaryDevice) {
|
||||
window.Whisper.events.on('refreshLinkedDeviceList', async () => {
|
||||
this.refreshLinkedDevice();
|
||||
void this.refreshLinkedDevice();
|
||||
});
|
||||
this.refreshLinkedDevice();
|
||||
void this.refreshLinkedDevice();
|
||||
}
|
||||
}
|
||||
|
||||
public refreshLinkedDevice() {
|
||||
const ourPubKey = window.textsecure.storage.user.getNumber();
|
||||
public async refreshLinkedDevice() {
|
||||
const ourPubKey = await UserUtil.getCurrentDevicePubKey();
|
||||
if (ourPubKey) {
|
||||
const pubKey = new PubKey(ourPubKey);
|
||||
const devices = await MultiDeviceProtocol.getSecondaryDevices(pubKey);
|
||||
|
||||
window.libloki.storage
|
||||
.getSecondaryDevicesFor(ourPubKey)
|
||||
.then((pubKeys: any) => {
|
||||
this.setState({
|
||||
disableLinkDeviceButton: pubKeys && pubKeys.length > 0,
|
||||
});
|
||||
});
|
||||
this.setState({ disableLinkDeviceButton: devices.length > 0 });
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
|
|
@ -15,6 +15,8 @@ import { SignalService } from './../protobuf';
|
|||
import { removeFromCache } from './cache';
|
||||
import { toNumber } from 'lodash';
|
||||
import { DataMessage } from '../session/messages/outgoing';
|
||||
import { MultiDeviceProtocol } from '../session/protocols';
|
||||
import { PubKey } from '../session/types';
|
||||
|
||||
export { handleEndSession, handleMediumGroupUpdate };
|
||||
|
||||
|
@ -265,7 +267,12 @@ async function handleSecondaryDeviceFriendRequest(pubKey: string) {
|
|||
if (!c || !(await c.isFriendWithAnyDevice())) {
|
||||
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;
|
||||
}
|
||||
|
@ -601,14 +608,11 @@ export async function handleDataMessage(
|
|||
|
||||
const source = envelope.senderIdentity || senderPubKey;
|
||||
|
||||
const isOwnDevice = async (pubkey: string) => {
|
||||
const primaryDevice = window.storage.get('primaryDevicePubKey');
|
||||
const secondaryDevices = await window.libloki.storage.getPairedDevicesFor(
|
||||
primaryDevice
|
||||
);
|
||||
const isOwnDevice = async (device: string) => {
|
||||
const pubKey = new PubKey(device);
|
||||
const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey);
|
||||
|
||||
const allDevices = [primaryDevice, ...secondaryDevices];
|
||||
return allDevices.includes(pubkey);
|
||||
return allDevices.some(d => PubKey.isEqual(d, pubKey));
|
||||
};
|
||||
|
||||
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
|
||||
let conversationId = id;
|
||||
|
||||
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey(
|
||||
source
|
||||
);
|
||||
|
||||
const primarySource =
|
||||
(authorisation && authorisation.primaryDevicePubKey) || source;
|
||||
|
||||
const primarySource = await MultiDeviceProtocol.getPrimaryDevice(source);
|
||||
if (isGroupMessage) {
|
||||
/* handle one part of the group logic here:
|
||||
handle requesting info of a new group,
|
||||
|
@ -789,7 +787,7 @@ export async function handleMessageEvent(event: any): Promise<void> {
|
|||
const shouldReturn = await preprocessGroupMessage(
|
||||
source,
|
||||
message.group,
|
||||
primarySource
|
||||
primarySource.key
|
||||
);
|
||||
|
||||
// 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
|
||||
conversationId = authorisation.primaryDevicePubKey;
|
||||
conversationId = primarySource.key;
|
||||
}
|
||||
|
||||
// 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 Protocols from './protocols';
|
||||
import * as Types from './types';
|
||||
|
||||
// TODO: Do we export class instances here?
|
||||
// E.g
|
||||
// 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...
|
||||
// We need to deprecate the multi device code we have in js and slowly transition to this file
|
||||
import _ from 'lodash';
|
||||
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 * 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 {
|
||||
MessageQueueInterface,
|
||||
|
@ -20,7 +18,7 @@ import {
|
|||
} from '../utils';
|
||||
import { PubKey } from '../types';
|
||||
import { MessageSender } from '.';
|
||||
import { SessionProtocol } from '../protocols';
|
||||
import { MultiDeviceProtocol, SessionProtocol } from '../protocols';
|
||||
import { UserUtil } from '../../util';
|
||||
|
||||
export class MessageQueue implements MessageQueueInterface {
|
||||
|
@ -35,8 +33,7 @@ export class MessageQueue implements MessageQueueInterface {
|
|||
}
|
||||
|
||||
public async sendUsingMultiDevice(user: PubKey, message: ContentMessage) {
|
||||
const userLinked = await getPairedDevicesFor(user.key);
|
||||
const userDevices = userLinked.map(d => new PubKey(d));
|
||||
const userDevices = await MultiDeviceProtocol.getAllDevices(user.key);
|
||||
|
||||
await this.sendMessageToDevices(userDevices, message);
|
||||
}
|
||||
|
@ -56,11 +53,10 @@ export class MessageQueue implements MessageQueueInterface {
|
|||
const currentDevice = await UserUtil.getCurrentDevicePubKey();
|
||||
|
||||
if (currentDevice) {
|
||||
const otherDevices = await getPairedDevicesFor(currentDevice);
|
||||
|
||||
const ourDevices = [currentDevice, ...otherDevices].map(
|
||||
device => new PubKey(device)
|
||||
const ourDevices = await MultiDeviceProtocol.getAllDevices(
|
||||
currentDevice
|
||||
);
|
||||
|
||||
await this.sendSyncMessage(message, ourDevices);
|
||||
|
||||
// Remove our devices from currentDevices
|
||||
|
|
|
@ -31,3 +31,6 @@ export class PubKey {
|
|||
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 UserUtils from '../../util/user';
|
||||
import {
|
||||
getAllConversations,
|
||||
getPrimaryDeviceFor,
|
||||
} from '../../../js/modules/data';
|
||||
|
||||
import { getAllConversations } from '../../../js/modules/data';
|
||||
import { ContentMessage, SyncMessage } from '../messages/outgoing';
|
||||
import { MultiDeviceProtocol } from '../protocols';
|
||||
|
||||
export function from(message: ContentMessage): SyncMessage | undefined {
|
||||
// const { timestamp, identifier } = message;
|
||||
|
@ -29,7 +26,7 @@ export async function getSyncContacts(): Promise<Array<any> | undefined> {
|
|||
return [];
|
||||
}
|
||||
|
||||
const primaryDevice = await getPrimaryDeviceFor(thisDevice);
|
||||
const primaryDevice = await MultiDeviceProtocol.getPrimaryDevice(thisDevice);
|
||||
const conversations = await getAllConversations({
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
});
|
||||
|
@ -61,7 +58,7 @@ export async function getSyncContacts(): Promise<Array<any> | undefined> {
|
|||
|
||||
const secondaryContacts = (await Promise.all(seondaryContactsPromise))
|
||||
// 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 _.uniqBy(
|
||||
|
|
|
@ -5,11 +5,7 @@ import { AdvancedSearchOptions, SearchOptions } from '../../types/Search';
|
|||
import { trigger } from '../../shims/events';
|
||||
import { getMessageModel } from '../../shims/Whisper';
|
||||
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
|
||||
import {
|
||||
getPrimaryDeviceFor,
|
||||
searchConversations,
|
||||
searchMessages,
|
||||
} from '../../../js/modules/data';
|
||||
import { searchConversations, searchMessages } from '../../../js/modules/data';
|
||||
import { makeLookup } from '../../util/makeLookup';
|
||||
|
||||
import {
|
||||
|
@ -19,6 +15,8 @@ import {
|
|||
RemoveAllConversationsActionType,
|
||||
SelectedConversationChangedActionType,
|
||||
} from './conversations';
|
||||
import { MultiDeviceProtocol } from '../../session/protocols';
|
||||
import { PubKey } from '../../session/types';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -283,15 +281,13 @@ async function queryConversationsAndContacts(
|
|||
query
|
||||
);
|
||||
|
||||
const ourPrimaryDevice = isSecondaryDevice
|
||||
? await getPrimaryDeviceFor(ourNumber)
|
||||
: ourNumber;
|
||||
const ourPrimaryDevice = await MultiDeviceProtocol.getPrimaryDevice(
|
||||
ourNumber
|
||||
);
|
||||
|
||||
const resultPrimaryDevices: Array<string | null> = await Promise.all(
|
||||
const resultPrimaryDevices = await Promise.all(
|
||||
searchResults.map(async conversation =>
|
||||
conversation.id === ourPrimaryDevice
|
||||
? Promise.resolve(ourPrimaryDevice)
|
||||
: getPrimaryDeviceFor(conversation.id)
|
||||
MultiDeviceProtocol.getPrimaryDevice(conversation.id)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -304,10 +300,13 @@ async function queryConversationsAndContacts(
|
|||
const primaryDevice = resultPrimaryDevices[i];
|
||||
|
||||
if (primaryDevice) {
|
||||
if (isSecondaryDevice && primaryDevice === ourPrimaryDevice) {
|
||||
if (
|
||||
isSecondaryDevice &&
|
||||
PubKey.isEqual(primaryDevice, ourPrimaryDevice)
|
||||
) {
|
||||
conversations.push(ourNumber);
|
||||
} else {
|
||||
conversations.push(primaryDevice);
|
||||
conversations.push(primaryDevice.key);
|
||||
}
|
||||
} else if (conversation.type === 'direct') {
|
||||
contacts.push(conversation.id);
|
||||
|
|
2
ts/window.d.ts
vendored
2
ts/window.d.ts
vendored
|
@ -46,7 +46,7 @@ declare global {
|
|||
libsignal: LibsignalProtocol;
|
||||
log: any;
|
||||
lokiFeatureFlags: any;
|
||||
lokiFileServerAPI: any;
|
||||
lokiFileServerAPI: LokiFileServerInstance;
|
||||
lokiMessageAPI: LokiMessageAPI;
|
||||
lokiPublicChatAPI: LokiPublicChatFactoryAPI;
|
||||
mnemonic: any;
|
||||
|
|
Loading…
Reference in a new issue