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.
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import { isArray } from 'lodash';
|
|
import pRetry from 'p-retry';
|
|
import { Snode } from '../../../data/types';
|
|
import { SwarmForSubRequest } from './SnodeRequestTypes';
|
|
import { doSnodeBatchRequest } from './batchRequest';
|
|
import { GetNetworkTime } from './getNetworkTime';
|
|
import { getRandomSnode } from './snodePool';
|
|
|
|
function buildSwarmForSubRequests(pubkey: string): Array<SwarmForSubRequest> {
|
|
return [{ method: 'get_swarm', params: { pubkey } }];
|
|
}
|
|
|
|
/**
|
|
* get snodes for pubkey from random snode. Uses an existing snode
|
|
*/
|
|
async function requestSnodesForPubkeyWithTargetNodeRetryable(
|
|
pubkey: string,
|
|
targetNode: Snode
|
|
): Promise<Array<Snode>> {
|
|
const subRequests = buildSwarmForSubRequests(pubkey);
|
|
|
|
const result = await doSnodeBatchRequest(subRequests, targetNode, 4000, pubkey);
|
|
|
|
if (!result || !result.length) {
|
|
window?.log?.warn(
|
|
`SessionSnodeAPI::requestSnodesForPubkeyWithTargetNodeRetryable - sessionRpc on ${targetNode.ip}:${targetNode.port} returned falsish value`,
|
|
result
|
|
);
|
|
throw new Error('requestSnodesForPubkeyWithTargetNodeRetryable: Invalid result');
|
|
}
|
|
|
|
const firstResult = result[0];
|
|
|
|
if (firstResult.code !== 200) {
|
|
window?.log?.warn('Status is not 200 for get_swarm but: ', firstResult.code);
|
|
throw new Error('requestSnodesForPubkeyWithTargetNodeRetryable: Invalid status code');
|
|
}
|
|
|
|
try {
|
|
const body = firstResult.body;
|
|
if (!body.snodes || !isArray(body.snodes) || !body.snodes.length) {
|
|
window?.log?.warn(
|
|
`SessionSnodeAPI::requestSnodesForPubkeyRetryable - sessionRpc on ${targetNode.ip}:${targetNode.port} returned falsish value for snodes`,
|
|
result
|
|
);
|
|
throw new Error('requestSnodesForPubkey: Invalid json (empty)');
|
|
}
|
|
|
|
const snodes = body.snodes.filter((tSnode: any) => tSnode.ip !== '0.0.0.0');
|
|
GetNetworkTime.handleTimestampOffsetFromNetwork('get_swarm', body.t);
|
|
return snodes;
|
|
} catch (e) {
|
|
throw new Error('Invalid json');
|
|
}
|
|
}
|
|
|
|
async function requestSnodesForPubkeyWithTargetNode(
|
|
pubKey: string,
|
|
targetNode: Snode
|
|
): Promise<Array<Snode>> {
|
|
// don't catch exception in here. we want them to bubble up
|
|
|
|
// this is the level where our targetNode is supposed to be valid. We retry a few times with this one.
|
|
// if all our retries fails, we retry from the caller of this function with a new target node.
|
|
return pRetry(
|
|
async () => {
|
|
return requestSnodesForPubkeyWithTargetNodeRetryable(pubKey, targetNode);
|
|
},
|
|
{
|
|
retries: 3,
|
|
factor: 2,
|
|
minTimeout: 100,
|
|
maxTimeout: 2000,
|
|
onFailedAttempt: e => {
|
|
window?.log?.warn(
|
|
`requestSnodesForPubkeyWithTargetNode attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
|
);
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async function requestSnodesForPubkeyRetryable(pubKey: string): Promise<Array<Snode>> {
|
|
// don't catch exception in here. we want them to bubble up
|
|
|
|
// this is the level where our targetNode is not yet known. We retry a few times with a new one every time.
|
|
// the idea is that the requestSnodesForPubkeyWithTargetNode will remove a failing targetNode
|
|
return pRetry(
|
|
async () => {
|
|
const targetNode = await getRandomSnode();
|
|
|
|
return requestSnodesForPubkeyWithTargetNode(pubKey, targetNode);
|
|
},
|
|
{
|
|
retries: 3,
|
|
factor: 2,
|
|
minTimeout: 100,
|
|
maxTimeout: 4000,
|
|
onFailedAttempt: e => {
|
|
window?.log?.warn(
|
|
`requestSnodesForPubkeyRetryable attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
|
);
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function requestSnodesForPubkeyFromNetwork(pubKey: string): Promise<Array<Snode>> {
|
|
try {
|
|
// catch exception in here only.
|
|
// the idea is that the pretry will retry a few times each calls, except if an AbortError is thrown.
|
|
|
|
// if all retry fails, we will end up in the catch below when the last exception thrown
|
|
return await requestSnodesForPubkeyRetryable(pubKey);
|
|
} catch (e) {
|
|
window?.log?.error('SessionSnodeAPI::requestSnodesForPubkey - error', e);
|
|
|
|
return [];
|
|
}
|
|
}
|