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 [];
 | |
|   }
 | |
| }
 |