You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/js/modules/loki_snode_api.js

122 lines
3.6 KiB
JavaScript

/* eslint-disable class-methods-use-this */
/* global window, Buffer, StringView, dcodeIO */
class LokiSnodeAPI {
// ************** NOTE ***************
// This is not used by anything yet,
// but should be. Do not remove!!!
// ***********************************
async getLnsMapping(lnsName, timeout) {
// Returns { pubkey, error }
// pubkey is
// undefined when unconfirmed or no mapping found
// string when found
// timeout parameter optional (ms)
// How many nodes to fetch data from?
const numRequests = 5;
// How many nodes must have the same response value?
const numRequiredConfirms = 3;
let ciphertextHex;
let pubkey;
let error;
const _ = window.Lodash;
const input = Buffer.from(lnsName);
const output = await window.blake2b(input);
const nameHash = dcodeIO.ByteBuffer.wrap(output).toString('base64');
// Timeouts
const maxTimeoutVal = 2 ** 31 - 1;
const timeoutPromise = () =>
new Promise((_resolve, reject) => setTimeout(() => reject(), timeout || maxTimeoutVal));
// Get nodes capable of doing LNS
const lnsNodes = await window.SnodePool.getNodesMinVersion(
window.CONSTANTS.LNS_CAPABLE_NODES_VERSION
);
// Enough nodes?
if (lnsNodes.length < numRequiredConfirms) {
error = { lnsTooFewNodes: window.i18n('lnsTooFewNodes') };
return { pubkey, error };
}
const confirmedNodes = [];
// Promise is only resolved when a consensus is found
let cipherResolve;
const cipherPromise = () =>
new Promise(resolve => {
cipherResolve = resolve;
});
const decryptHex = async cipherHex => {
const ciphertext = new Uint8Array(StringView.hexToArrayBuffer(cipherHex));
const res = await window.decryptLnsEntry(lnsName, ciphertext);
const publicKey = StringView.arrayBufferToHex(res);
return publicKey;
};
const fetchFromNode = async node => {
const res = await window.NewSnodeAPI._requestLnsMapping(node, nameHash);
// Do validation
if (res && res.result && res.result.status === 'OK') {
const hasMapping = res.result.entries && res.result.entries.length > 0;
const resValue = hasMapping ? res.result.entries[0].encrypted_value : null;
confirmedNodes.push(resValue);
if (confirmedNodes.length >= numRequiredConfirms) {
if (ciphertextHex) {
// Result already found, dont worry
return;
}
const [winner, count] = _.maxBy(_.entries(_.countBy(confirmedNodes)), x => x[1]);
if (count >= numRequiredConfirms) {
ciphertextHex = winner === String(null) ? null : winner;
// null represents no LNS mapping
if (ciphertextHex === null) {
error = { lnsMappingNotFound: window.i18n('lnsMappingNotFound') };
}
cipherResolve({ ciphertextHex });
}
}
}
};
const nodes = lnsNodes.splice(0, numRequests);
// Start fetching from nodes
nodes.forEach(node => fetchFromNode(node));
// Timeouts (optional parameter)
// Wait for cipher to be found; race against timeout
// eslint-disable-next-line more/no-then
await Promise.race([cipherPromise, timeoutPromise].map(f => f()))
.then(async () => {
if (ciphertextHex !== null) {
pubkey = await decryptHex(ciphertextHex);
}
})
.catch(() => {
error = { lnsLookupTimeout: window.i18n('lnsLookupTimeout') };
});
return { pubkey, error };
}
}
module.exports = LokiSnodeAPI;