Skip to content

Commit f8c585b

Browse files
committed
refactor(metrics): move isPublicEndpointUrl to background API
1 parent 2f1404a commit f8c585b

File tree

10 files changed

+196
-448
lines changed

10 files changed

+196
-448
lines changed

app/scripts/lib/network-controller/messenger-action-handlers.test.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HttpError } from '@metamask/controller-utils';
2-
import * as networkUtilsModule from '../../../../shared/lib/network-utils';
2+
import * as utilModule from '../util';
33
import {
44
onRpcEndpointDegraded,
55
onRpcEndpointUnavailable,
@@ -14,8 +14,8 @@ describe('onRpcEndpointUnavailable', () => {
1414
Parameters<typeof networkControllerUtilsModule.shouldCreateRpcServiceEvents>
1515
>;
1616
let isPublicEndpointUrlMock: jest.SpyInstance<
17-
ReturnType<typeof networkUtilsModule.isPublicEndpointUrl>,
18-
Parameters<typeof networkUtilsModule.isPublicEndpointUrl>
17+
ReturnType<typeof utilModule.isPublicEndpointUrl>,
18+
Parameters<typeof utilModule.isPublicEndpointUrl>
1919
>;
2020

2121
beforeEach(() => {
@@ -24,10 +24,7 @@ describe('onRpcEndpointUnavailable', () => {
2424
'shouldCreateRpcServiceEvents',
2525
);
2626

27-
isPublicEndpointUrlMock = jest.spyOn(
28-
networkUtilsModule,
29-
'isPublicEndpointUrl',
30-
);
27+
isPublicEndpointUrlMock = jest.spyOn(utilModule, 'isPublicEndpointUrl');
3128
});
3229

3330
it('calls shouldCreateRpcServiceEvents with the correct parameters', () => {
@@ -170,8 +167,8 @@ describe('onRpcEndpointDegraded', () => {
170167
Parameters<typeof networkControllerUtilsModule.shouldCreateRpcServiceEvents>
171168
>;
172169
let isPublicEndpointUrlMock: jest.SpyInstance<
173-
ReturnType<typeof networkUtilsModule.isPublicEndpointUrl>,
174-
Parameters<typeof networkUtilsModule.isPublicEndpointUrl>
170+
ReturnType<typeof utilModule.isPublicEndpointUrl>,
171+
Parameters<typeof utilModule.isPublicEndpointUrl>
175172
>;
176173

177174
beforeEach(() => {
@@ -180,10 +177,7 @@ describe('onRpcEndpointDegraded', () => {
180177
'shouldCreateRpcServiceEvents',
181178
);
182179

183-
isPublicEndpointUrlMock = jest.spyOn(
184-
networkUtilsModule,
185-
'isPublicEndpointUrl',
186-
);
180+
isPublicEndpointUrlMock = jest.spyOn(utilModule, 'isPublicEndpointUrl');
187181
});
188182

189183
it('calls shouldCreateRpcServiceEvents with the correct parameters', () => {

app/scripts/lib/network-controller/messenger-action-handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
MetaMetricsEventName,
66
} from '../../../../shared/constants/metametrics';
77
import { onlyKeepHost } from '../../../../shared/lib/only-keep-host';
8-
import { isPublicEndpointUrl } from '../../../../shared/lib/network-utils';
8+
import { isPublicEndpointUrl } from '../util';
99
import MetaMetricsController from '../../controllers/metametrics-controller';
1010
import { shouldCreateRpcServiceEvents } from './utils';
1111

app/scripts/lib/util.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
extractRpcDomain,
3131
isKnownDomain,
3232
initializeRpcProviderDomains,
33+
isPublicEndpointUrl,
3334
} from './util';
3435

3536
// Mock the module
@@ -608,4 +609,26 @@ describe('app utils', () => {
608609
expect(extractRpcDomain('https://')).toBe('invalid');
609610
});
610611
});
612+
613+
describe('isPublicEndpointUrl', () => {
614+
const MOCK_INFURA_PROJECT_ID = 'test-project-id';
615+
616+
it('should return true for Infura URLs', () => {
617+
expect(
618+
isPublicEndpointUrl(
619+
`https://mainnet.infura.io/v3/${MOCK_INFURA_PROJECT_ID}`,
620+
MOCK_INFURA_PROJECT_ID,
621+
),
622+
).toBe(true);
623+
});
624+
625+
it('should return false for unknown URLs', () => {
626+
expect(
627+
isPublicEndpointUrl(
628+
'https://unknown.example.com',
629+
MOCK_INFURA_PROJECT_ID,
630+
),
631+
).toBe(false);
632+
});
633+
});
611634
});

app/scripts/lib/util.ts

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network';
2626
import { stripHexPrefix } from '../../../shared/modules/hexstring-utils';
2727
import { getMethodDataAsync } from '../../../shared/lib/four-byte';
2828
import {
29-
initializeChainlistDomains,
30-
isChainlistDomain,
29+
getSafeChainsListFromCacheOnly,
30+
getIsMetaMaskInfuraEndpointUrl,
31+
getIsQuicknodeEndpointUrl,
32+
KNOWN_CUSTOM_ENDPOINT_URLS,
3133
} from '../../../shared/lib/network-utils';
3234

3335
/**
@@ -477,12 +479,120 @@ export function getConversionRatesForNativeAsset({
477479
return conversionRateResult;
478480
}
479481

480-
// Re-export chainlist domain functions for backward compatibility
481-
// These were moved to shared/lib/network-utils.ts
482-
export {
483-
initializeChainlistDomains as initializeRpcProviderDomains,
484-
isChainlistDomain as isKnownDomain,
485-
};
482+
// Cache for known domains from eth-chainlist
483+
let knownDomainsSet: Set<string> | null = null;
484+
let initPromise: Promise<void> | null = null;
485+
486+
/**
487+
* Extracts the hostname from a URL.
488+
*
489+
* @param url - The URL to extract the hostname from.
490+
* @returns The lowercase hostname, or null if the URL is invalid.
491+
*/
492+
function extractHostname(url: string): string | null {
493+
try {
494+
const parsedUrl = new URL(url);
495+
return parsedUrl.hostname.toLowerCase();
496+
} catch {
497+
return null;
498+
}
499+
}
500+
501+
/**
502+
* Initialize the set of known domains from the eth-chainlist cache.
503+
* This should be called once at startup in the background context.
504+
*
505+
* @returns A promise that resolves when initialization is complete.
506+
*/
507+
export async function initializeRpcProviderDomains(): Promise<void> {
508+
if (initPromise) {
509+
return initPromise;
510+
}
511+
512+
initPromise = (async () => {
513+
try {
514+
const chainsList = await getSafeChainsListFromCacheOnly();
515+
knownDomainsSet = new Set<string>();
516+
517+
for (const chain of chainsList) {
518+
if (chain.rpc && Array.isArray(chain.rpc)) {
519+
for (const rpcUrl of chain.rpc) {
520+
const hostname = extractHostname(rpcUrl);
521+
if (hostname) {
522+
knownDomainsSet.add(hostname);
523+
}
524+
}
525+
}
526+
}
527+
} catch (error) {
528+
console.error('Error initializing known domains:', error);
529+
knownDomainsSet = new Set<string>();
530+
}
531+
})();
532+
533+
return initPromise;
534+
}
535+
536+
/**
537+
* Check if a domain is in the known domains list (eth-chainlist).
538+
*
539+
* @param domain - The domain to check.
540+
* @returns True if the domain is found in the chainlist cache.
541+
*/
542+
export function isKnownDomain(domain: string): boolean {
543+
if (!domain) {
544+
return false;
545+
}
546+
return knownDomainsSet?.has(domain.toLowerCase()) ?? false;
547+
}
548+
549+
/**
550+
* Check if an RPC endpoint URL's domain is defined in eth-chainlist.
551+
*
552+
* @param endpointUrl - The URL of the RPC endpoint.
553+
* @returns True if the endpoint's domain is in the chainlist.
554+
*/
555+
function isChainlistEndpointUrl(endpointUrl: string): boolean {
556+
const hostname = extractHostname(endpointUrl);
557+
if (!hostname) {
558+
return false;
559+
}
560+
return isKnownDomain(hostname);
561+
}
562+
563+
/**
564+
* Some URLs that users add as networks refer to private servers, and we do not
565+
* want to report these in Segment (or any other data collection service). This
566+
* function returns whether the given RPC endpoint is safe to share.
567+
*
568+
* This function is only available in the background context where the chainlist
569+
* domains are initialized. UI should call the background API method instead.
570+
*
571+
* @param endpointUrl - The URL of the endpoint.
572+
* @param infuraProjectId - Our Infura project ID.
573+
* @returns True if the endpoint URL is safe to share with external data
574+
* collection services, false otherwise.
575+
*/
576+
export function isPublicEndpointUrl(
577+
endpointUrl: string,
578+
infuraProjectId: string,
579+
): boolean {
580+
const isMetaMaskInfuraEndpointUrl = getIsMetaMaskInfuraEndpointUrl(
581+
endpointUrl,
582+
infuraProjectId,
583+
);
584+
const isQuicknodeEndpointUrl = getIsQuicknodeEndpointUrl(endpointUrl);
585+
const isKnownCustomEndpointUrl =
586+
KNOWN_CUSTOM_ENDPOINT_URLS.includes(endpointUrl);
587+
const isChainlistEndpoint = isChainlistEndpointUrl(endpointUrl);
588+
589+
return (
590+
isMetaMaskInfuraEndpointUrl ||
591+
isQuicknodeEndpointUrl ||
592+
isKnownCustomEndpointUrl ||
593+
isChainlistEndpoint
594+
);
595+
}
486596

487597
/**
488598
* Extracts the domain from an RPC endpoint URL with privacy considerations
@@ -518,12 +628,12 @@ export function extractRpcDomain(
518628
}
519629
}
520630

521-
// Use the provided test domains if available, otherwise use isChainlistDomain
631+
// Use the provided test domains if available, otherwise use isKnownDomain
522632
if (knownDomainsForTesting) {
523633
if (knownDomainsForTesting.has(url.hostname.toLowerCase())) {
524634
return url.hostname.toLowerCase();
525635
}
526-
} else if (isChainlistDomain(url.hostname)) {
636+
} else if (isKnownDomain(url.hostname)) {
527637
return url.hostname.toLowerCase();
528638
}
529639

app/scripts/metamask-controller.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ import {
264264
getMethodDataName,
265265
previousValueComparator,
266266
initializeRpcProviderDomains,
267+
isPublicEndpointUrl,
267268
getPlatform,
268269
getBooleanFlag,
269270
} from './lib/util';
@@ -2498,6 +2499,8 @@ export default class MetamaskController extends EventEmitter {
24982499
getProviderConfig({
24992500
metamask: this.networkController.state,
25002501
}),
2502+
isPublicEndpointUrl: (endpointUrl) =>
2503+
isPublicEndpointUrl(endpointUrl, this.opts.infuraProjectId),
25012504
grantPermissionsIncremental:
25022505
this.permissionController.grantPermissionsIncremental.bind(
25032506
this.permissionController,

0 commit comments

Comments
 (0)