Added DNS resolution error for when lokinet isn't working, now keeping track of the nodes that have been queried and not trying them again

pull/147/head
Beaudan 7 years ago
parent 561d60cfd5
commit c83661ce3f

@ -5,6 +5,7 @@ const fetch = require('node-fetch');
// Will be raised (to 3?) when we get more nodes // Will be raised (to 3?) when we get more nodes
const MINIMUM_SUCCESSFUL_REQUESTS = 2; const MINIMUM_SUCCESSFUL_REQUESTS = 2;
class LokiMessageAPI { class LokiMessageAPI {
constructor({ messageServerPort }) { constructor({ messageServerPort }) {
this.messageServerPort = messageServerPort ? `:${messageServerPort}` : ''; this.messageServerPort = messageServerPort ? `:${messageServerPort}` : '';
@ -33,7 +34,8 @@ class LokiMessageAPI {
// Something went horribly wrong // Something went horribly wrong
throw err; throw err;
} }
let completedRequests = 0; const completedNodes = [];
let canResolve = true;
const doRequest = async nodeUrl => { const doRequest = async nodeUrl => {
// TODO: Confirm sensible timeout // TODO: Confirm sensible timeout
@ -60,8 +62,15 @@ class LokiMessageAPI {
try { try {
response = await fetch(options.url, fetchOptions); response = await fetch(options.url, fetchOptions);
} catch (e) { } catch (e) {
if (e.code === 'ENOTFOUND') {
// TODO: Handle the case where lokinet is not working
canResolve = false;
return;
}
log.error(options.type, options.url, 0, 'Error sending message'); log.error(options.type, options.url, 0, 'Error sending message');
window.LokiSnodeAPI.unreachableNode(pubKey, nodeUrl); if (window.LokiSnodeAPI.unreachableNode(pubKey, nodeUrl)) {
completedNodes.push(nodeUrl);
}
return; return;
} }
@ -78,7 +87,7 @@ class LokiMessageAPI {
} }
if (response.status >= 0 && response.status < 400) { if (response.status >= 0 && response.status < 400) {
completedRequests += 1; completedNodes.push(nodeUrl);
return; return;
} }
log.error( log.error(
@ -91,14 +100,28 @@ class LokiMessageAPI {
}; };
let swarmNodes; let swarmNodes;
while (completedRequests < MINIMUM_SUCCESSFUL_REQUESTS) { while (completedNodes.length < MINIMUM_SUCCESSFUL_REQUESTS) {
if (!canResolve) {
throw new window.textsecure.DNSResolutionError('Sending messages');
}
try { try {
swarmNodes = await window.LokiSnodeAPI.getSwarmNodesByPubkey(pubKey); swarmNodes = await window.LokiSnodeAPI.getSwarmNodesByPubkey(pubKey);
// Filter out the nodes we have already got responses from
swarmNodes = Object.keys(swarmNodes)
.filter(node => !(node in completedNodes))
.reduce(
// eslint-disable-next-line no-loop-func
(obj, node) => ({
...obj,
[node]: swarmNodes[node],
}),
{}
);
} catch (e) { } catch (e) {
throw new window.textsecure.EmptySwarmError(pubKey, e); throw new window.textsecure.EmptySwarmError(pubKey, e);
} }
if (!swarmNodes || swarmNodes.size === 0) { if (!swarmNodes || swarmNodes.size === 0) {
if (completedRequests !== 0) { if (completedNodes.length !== 0) {
// TODO: Decide how to handle some completed requests but not enough // TODO: Decide how to handle some completed requests but not enough
return; return;
} }
@ -108,7 +131,8 @@ class LokiMessageAPI {
); );
} }
const remainingRequests = MINIMUM_SUCCESSFUL_REQUESTS - completedRequests; const remainingRequests =
MINIMUM_SUCCESSFUL_REQUESTS - completedNodes.length;
await Promise.all( await Promise.all(
Array.from(swarmNodes) Array.from(swarmNodes)
.splice(0, remainingRequests) .splice(0, remainingRequests)
@ -119,7 +143,8 @@ class LokiMessageAPI {
async retrieveMessages(callback) { async retrieveMessages(callback) {
const ourKey = window.textsecure.storage.user.getNumber(); const ourKey = window.textsecure.storage.user.getNumber();
let completedRequests = 0; const completedNodes = [];
let canResolve = true;
const doRequest = async (nodeUrl, nodeData) => { const doRequest = async (nodeUrl, nodeData) => {
// TODO: Confirm sensible timeout // TODO: Confirm sensible timeout
@ -147,6 +172,11 @@ class LokiMessageAPI {
try { try {
response = await fetch(options.url, fetchOptions); response = await fetch(options.url, fetchOptions);
} catch (e) { } catch (e) {
if (e.code === 'ENOTFOUND') {
// TODO: Handle the case where lokinet is not working
canResolve = false;
return;
}
// TODO: Maybe we shouldn't immediately delete? // TODO: Maybe we shouldn't immediately delete?
// And differentiate between different connectivity issues // And differentiate between different connectivity issues
log.error( log.error(
@ -155,7 +185,9 @@ class LokiMessageAPI {
0, 0,
`Error retrieving messages from ${nodeUrl}` `Error retrieving messages from ${nodeUrl}`
); );
window.LokiSnodeAPI.unreachableNode(ourKey, nodeUrl); if (window.LokiSnodeAPI.unreachableNode(ourKey, nodeUrl)) {
completedNodes.push(nodeUrl);
}
return; return;
} }
@ -170,7 +202,7 @@ class LokiMessageAPI {
} else { } else {
result = await response.text(); result = await response.text();
} }
completedRequests += 1; completedNodes.push(nodeUrl);
if (response.status === 200) { if (response.status === 200) {
if (result.lastHash) { if (result.lastHash) {
@ -183,28 +215,43 @@ class LokiMessageAPI {
log.error(options.type, options.url, response.status, 'Error'); log.error(options.type, options.url, response.status, 'Error');
}; };
while (completedRequests < MINIMUM_SUCCESSFUL_REQUESTS) { while (completedNodes.length < MINIMUM_SUCCESSFUL_REQUESTS) {
if (!canResolve) {
throw new window.textsecure.DNSResolutionError('Retrieving messages');
}
let ourSwarmNodes; let ourSwarmNodes;
try { try {
ourSwarmNodes = await window.LokiSnodeAPI.getOurSwarmNodes(); ourSwarmNodes = await window.LokiSnodeAPI.getOurSwarmNodes();
// Filter out the nodes we have already got responses from
ourSwarmNodes = Object.keys(ourSwarmNodes)
.filter(node => !(node in completedNodes))
.reduce(
// eslint-disable-next-line no-loop-func
(obj, node) => ({
...obj,
[node]: ourSwarmNodes[node],
}),
{}
);
} catch (e) { } catch (e) {
throw window.textsecure.EmptySwarmError( throw new window.textsecure.EmptySwarmError(
window.textsecure.storage.user.getNumber(), window.textsecure.storage.user.getNumber(),
e e
); );
} }
if (!ourSwarmNodes || Object.keys(ourSwarmNodes).length === 0) { if (!ourSwarmNodes || Object.keys(ourSwarmNodes).length === 0) {
if (completedRequests !== 0) { if (completedNodes.length !== 0) {
// TODO: Decide how to handle some completed requests but not enough // TODO: Decide how to handle some completed requests but not enough
return; return;
} }
throw window.textsecure.EmptySwarmError( throw new window.textsecure.EmptySwarmError(
window.textsecure.storage.user.getNumber(), window.textsecure.storage.user.getNumber(),
new Error('Ran out of swarm nodes to query') new Error('Ran out of swarm nodes to query')
); );
} }
const remainingRequests = MINIMUM_SUCCESSFUL_REQUESTS - completedRequests; const remainingRequests =
MINIMUM_SUCCESSFUL_REQUESTS - completedNodes.length;
await Promise.all( await Promise.all(
Object.entries(ourSwarmNodes) Object.entries(ourSwarmNodes)
.splice(0, remainingRequests) .splice(0, remainingRequests)

@ -45,7 +45,7 @@ class LokiSnodeAPI {
if (this.ourSwarmNodes[nodeUrl].failureCount >= FAILURE_THRESHOLD) { if (this.ourSwarmNodes[nodeUrl].failureCount >= FAILURE_THRESHOLD) {
delete this.ourSwarmNodes[nodeUrl]; delete this.ourSwarmNodes[nodeUrl];
} }
return; return false;
} }
if (!this.contactSwarmNodes[nodeUrl]) { if (!this.contactSwarmNodes[nodeUrl]) {
this.contactSwarmNodes[nodeUrl] = { this.contactSwarmNodes[nodeUrl] = {
@ -55,7 +55,7 @@ class LokiSnodeAPI {
this.contactSwarmNodes[nodeUrl].failureCount += 1; this.contactSwarmNodes[nodeUrl].failureCount += 1;
} }
if (this.contactSwarmNodes[nodeUrl].failureCount < FAILURE_THRESHOLD) { if (this.contactSwarmNodes[nodeUrl].failureCount < FAILURE_THRESHOLD) {
return; return false;
} }
const conversation = window.ConversationController.get(pubKey); const conversation = window.ConversationController.get(pubKey);
const swarmNodes = conversation.get('swarmNodes'); const swarmNodes = conversation.get('swarmNodes');
@ -70,6 +70,7 @@ class LokiSnodeAPI {
); );
delete this.contactSwarmNodes[nodeUrl]; delete this.contactSwarmNodes[nodeUrl];
} }
return true;
} }
updateLastHash(nodeUrl, hash) { updateLastHash(nodeUrl, hash) {

@ -157,6 +157,16 @@
} }
inherit(ReplayableError, PoWError); inherit(ReplayableError, PoWError);
function DNSResolutionError(message) {
// eslint-disable-next-line prefer-destructuring
ReplayableError.call(this, {
name: 'DNSResolutionError',
message: `Error resolving url: ${message}`,
});
}
inherit(ReplayableError, DNSResolutionError);
window.textsecure.UnregisteredUserError = UnregisteredUserError; window.textsecure.UnregisteredUserError = UnregisteredUserError;
window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.SendMessageNetworkError = SendMessageNetworkError;
window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError; window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError;
@ -167,4 +177,5 @@
window.textsecure.SignedPreKeyRotationError = SignedPreKeyRotationError; window.textsecure.SignedPreKeyRotationError = SignedPreKeyRotationError;
window.textsecure.PoWError = PoWError; window.textsecure.PoWError = PoWError;
window.textsecure.EmptySwarmError = EmptySwarmError; window.textsecure.EmptySwarmError = EmptySwarmError;
window.textsecure.DNSResolutionError = DNSResolutionError;
})(); })();

Loading…
Cancel
Save