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/ts/session/apis/snode_api/getSwarmFor.ts

121 lines
4.0 KiB
TypeScript

import { isArray } from 'lodash';
import pRetry from 'p-retry';
import { Snode } from '../../../data/data';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { getRandomSnode } from './snodePool';
import { SwarmForSubRequest } from './SnodeRequestTypes';
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 [];
}
}