/* 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;