Socket
Socket
Sign inDemoInstall

@metamask/ppom-validator

Package Overview
Dependencies
Maintainers
10
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@metamask/ppom-validator - npm Package Compare versions

Comparing version 0.1.2 to 0.2.0

dist/util.d.ts

9

CHANGELOG.md

@@ -9,2 +9,8 @@ # Changelog

## [0.2.0]
### Changed
- Adding code to verify signature of data blobs fetched from CDN ([#35](https://github.com/MetaMask/ppom-validator/pull/35))
- Rate limit requests to the provider ([#28](https://github.com/MetaMask/ppom-validator/pull/28))
- Validate path of data files ([#27](https://github.com/MetaMask/ppom-validator/pull/27))
## [0.1.2]

@@ -33,5 +39,6 @@ ### Changed

[Unreleased]: https://github.com/MetaMask/ppom-validator/compare/v0.1.2...HEAD
[Unreleased]: https://github.com/MetaMask/ppom-validator/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/MetaMask/ppom-validator/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/MetaMask/ppom-validator/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/MetaMask/ppom-validator/compare/v0.0.1...v0.1.1
[0.0.1]: https://github.com/MetaMask/ppom-validator/releases/tag/v0.0.1

26

dist/ppom-controller.d.ts

@@ -12,2 +12,3 @@ import { BaseControllerV2, RestrictedControllerMessenger } from '@metamask/base-controller';

filePath: string;
signature: string;
};

@@ -26,7 +27,4 @@ /**

* @property storageMetadata - Metadata of files storaged in storage.
* @property providerRequestLimit - Number of requests in last 5 minutes that PPOM can make.
* @property providerRequests - Array of timestamps in last 5 minutes when request was made from PPOM to provider.
*/
export declare type PPOMState = {
chainId: string;
chainStatus: Record<string, {

@@ -39,7 +37,2 @@ chainId: string;

storageMetadata: FileMetadataList;
refreshInterval: number;
fileScheduleInterval: number;
providerRequestLimit: number;
providerRequests: number[];
securityAlertsEnabled: boolean;
versionFileETag?: string;

@@ -87,6 +80,10 @@ };

* @param options.cdnBaseUrl - Base URL for the CDN.
* @param options.providerRequestLimit - Limit of number of requests that can be sent to provider per transaction.
* @param options.dataUpdateDuration - Duration after which data is fetched again.
* @param options.fileFetchScheduleDuration - Duration after which next data file is fetched.
* @param options.state - Initial state of the controller.
* @param options.blockaidPublicKey - Public key of blcokaid for verifying signatures of data files.
* @returns The PPOMController instance.
*/
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, state, }: {
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, providerRequestLimit, dataUpdateDuration, fileFetchScheduleDuration, state, blockaidPublicKey, }: {
chainId: string;

@@ -101,3 +98,7 @@ onNetworkChange: (callback: (networkState: any) => void) => void;

cdnBaseUrl: string;
providerRequestLimit?: number;
dataUpdateDuration?: number;
fileFetchScheduleDuration?: number;
state?: PPOMState;
blockaidPublicKey: string;
});

@@ -107,9 +108,4 @@ /**

* This function will acquire mutex lock and invoke internal method #updatePPOM.
*
* @param options - Options.
* @param options.updateForAllChains - True is update if required to be done for all chains in cache.
*/
updatePPOM({ updateForAllChains }?: {
updateForAllChains: boolean;
}): Promise<void>;
updatePPOM(): Promise<void>;
/**

@@ -116,0 +112,0 @@ * Use the PPOM.

@@ -13,3 +13,3 @@ "use strict";

};
var _PPOMController_instances, _PPOMController_ppom, _PPOMController_provider, _PPOMController_storage, _PPOMController_refreshDataInterval, _PPOMController_fileScheduleInterval, _PPOMController_ppomMutex, _PPOMController_ppomProvider, _PPOMController_cdnBaseUrl, _PPOMController_registerMessageHandlers, _PPOMController_maybeUpdatePPOM, _PPOMController_shouldUpdate, _PPOMController_updatePPOM, _PPOMController_updateVersionInfo, _PPOMController_checkFilePresentInStorage, _PPOMController_getFile, _PPOMController_setChainIdDataFetched, _PPOMController_getNewFilesForCurrentChain, _PPOMController_getListOfFilesToBeFetched, _PPOMController_deleteOldChainIds, _PPOMController_getNewFilesForAllChains, _PPOMController_getAPIResponse, _PPOMController_fetchVersionInfo, _PPOMController_fetchBlob, _PPOMController_jsonRpcRequest, _PPOMController_getPPOM, _PPOMController_onFileScheduledInterval, _PPOMController_scheduleFileDownloadForAllChains;
var _PPOMController_instances, _PPOMController_ppom, _PPOMController_provider, _PPOMController_storage, _PPOMController_refreshDataInterval, _PPOMController_fileScheduleInterval, _PPOMController_ppomMutex, _PPOMController_ppomProvider, _PPOMController_cdnBaseUrl, _PPOMController_providerRequestLimit, _PPOMController_providerRequests, _PPOMController_chainId, _PPOMController_dataUpdateDuration, _PPOMController_fileFetchScheduleDuration, _PPOMController_securityAlertsEnabled, _PPOMController_blockaidPublicKey, _PPOMController_onNetworkChange, _PPOMController_onPreferenceChange, _PPOMController_registerMessageHandlers, _PPOMController_resetPPOM, _PPOMController_maybeUpdatePPOM, _PPOMController_isDataRequiredForCurrentChain, _PPOMController_updatePPOM, _PPOMController_updateVersionInfo, _PPOMController_checkFilePresentInStorage, _PPOMController_checkFilePath, _PPOMController_getFile, _PPOMController_setChainIdDataFetched, _PPOMController_getNewFilesForCurrentChain, _PPOMController_getListOfFilesToBeFetched, _PPOMController_deleteOldChainIds, _PPOMController_getNewFilesForAllChains, _PPOMController_getAPIResponse, _PPOMController_checkIfVersionInfoETagChanged, _PPOMController_fetchVersionInfo, _PPOMController_fetchBlob, _PPOMController_jsonRpcRequest, _PPOMController_getPPOM, _PPOMController_onDataUpdateDuration, _PPOMController_scheduleFileDownloadForAllChains;
Object.defineProperty(exports, "__esModule", { value: true });

@@ -21,6 +21,11 @@ exports.PPOMController = exports.NETWORK_CACHE_DURATION = exports.REFRESH_TIME_INTERVAL = void 0;

const ppom_storage_1 = require("./ppom-storage");
const util_1 = require("./util");
exports.REFRESH_TIME_INTERVAL = 1000 * 60 * 60 * 2;
const PROVIDER_REQUEST_LIMIT = 500;
const PROVIDER_REQUEST_LIMIT = 300;
const FILE_FETCH_SCHEDULE_INTERVAL = 1000 * 60 * 5;
exports.NETWORK_CACHE_DURATION = 1000 * 60 * 60 * 24 * 7;
const NETWORK_CACHE_LIMIT = {
MAX: 5,
MIN: 2,
};
// The following methods on provider are allowed to PPOM

@@ -30,2 +35,3 @@ const ALLOWED_PROVIDER_CALLS = [

'eth_blockNumber',
'eth_createAccessList',
'eth_getLogs',

@@ -48,10 +54,4 @@ 'eth_getFilterLogs',

versionInfo: { persist: false, anonymous: false },
chainId: { persist: false, anonymous: false },
chainStatus: { persist: false, anonymous: false },
storageMetadata: { persist: false, anonymous: false },
refreshInterval: { persist: false, anonymous: false },
fileScheduleInterval: { persist: false, anonymous: false },
providerRequestLimit: { persist: false, anonymous: false },
providerRequests: { persist: false, anonymous: false },
securityAlertsEnabled: { persist: false, anonymous: false },
versionFileETag: { persist: false, anonymous: false },

@@ -92,10 +92,13 @@ };

* @param options.cdnBaseUrl - Base URL for the CDN.
* @param options.providerRequestLimit - Limit of number of requests that can be sent to provider per transaction.
* @param options.dataUpdateDuration - Duration after which data is fetched again.
* @param options.fileFetchScheduleDuration - Duration after which next data file is fetched.
* @param options.state - Initial state of the controller.
* @param options.blockaidPublicKey - Public key of blcokaid for verifying signatures of data files.
* @returns The PPOMController instance.
*/
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, state, }) {
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, providerRequestLimit, dataUpdateDuration, fileFetchScheduleDuration, state, blockaidPublicKey, }) {
const initialState = {
versionInfo: state?.versionInfo ?? [],
storageMetadata: state?.storageMetadata ?? [],
chainId,
chainStatus: state?.chainStatus ?? {

@@ -108,7 +111,2 @@ [chainId]: {

},
refreshInterval: state?.refreshInterval ?? exports.REFRESH_TIME_INTERVAL,
fileScheduleInterval: state?.fileScheduleInterval ?? FILE_FETCH_SCHEDULE_INTERVAL,
providerRequestLimit: state?.providerRequestLimit ?? PROVIDER_REQUEST_LIMIT,
providerRequests: state?.providerRequests ?? [],
securityAlertsEnabled,
};

@@ -133,3 +131,18 @@ super({

_PPOMController_ppomProvider.set(this, void 0);
// base URL of the CDN
_PPOMController_cdnBaseUrl.set(this, void 0);
// Limit of number of requests ppom can send to the provider per transaction
_PPOMController_providerRequestLimit.set(this, void 0);
// Number of requests sent to provider by ppom for current transaction
_PPOMController_providerRequests.set(this, 0);
// id of current chain selected
_PPOMController_chainId.set(this, void 0);
// interval at which data files are refreshed, default will be 2 hours
_PPOMController_dataUpdateDuration.set(this, void 0);
// interval at which files for a network are fetched
_PPOMController_fileFetchScheduleDuration.set(this, void 0);
// true if user has enabled preference for blockaid security check
_PPOMController_securityAlertsEnabled.set(this, void 0);
_PPOMController_blockaidPublicKey.set(this, void 0);
__classPrivateFieldSet(this, _PPOMController_chainId, chainId, "f");
__classPrivateFieldSet(this, _PPOMController_provider, provider, "f");

@@ -150,40 +163,15 @@ __classPrivateFieldSet(this, _PPOMController_ppomProvider, ppomProvider, "f");

__classPrivateFieldSet(this, _PPOMController_cdnBaseUrl, cdnBaseUrl, "f");
onNetworkChange((networkControllerState) => {
const id = networkControllerState.providerConfig.chainId;
if (id === this.state.chainId) {
return;
}
let { chainStatus } = this.state;
const existingNetworkObject = chainStatus[id];
chainStatus = {
...chainStatus,
[id]: {
chainId: id,
lastVisited: new Date().getTime(),
dataFetched: existingNetworkObject?.dataFetched ?? false,
},
};
this.update((draftState) => {
draftState.chainId = id;
draftState.chainStatus = chainStatus;
});
});
onPreferencesChange((preferenceControllerState) => {
const blockaidEnabled = preferenceControllerState.securityAlertsEnabled;
if (blockaidEnabled === this.state.securityAlertsEnabled) {
return;
}
if (blockaidEnabled) {
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_scheduleFileDownloadForAllChains).call(this);
}
else {
clearInterval(__classPrivateFieldGet(this, _PPOMController_refreshDataInterval, "f"));
clearInterval(__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f"));
}
this.update((draftState) => {
draftState.securityAlertsEnabled = blockaidEnabled;
});
});
__classPrivateFieldSet(this, _PPOMController_providerRequestLimit, providerRequestLimit ?? PROVIDER_REQUEST_LIMIT, "f");
__classPrivateFieldSet(this, _PPOMController_dataUpdateDuration, dataUpdateDuration ?? exports.REFRESH_TIME_INTERVAL, "f");
__classPrivateFieldSet(this, _PPOMController_fileFetchScheduleDuration, fileFetchScheduleDuration ?? FILE_FETCH_SCHEDULE_INTERVAL, "f");
__classPrivateFieldSet(this, _PPOMController_securityAlertsEnabled, securityAlertsEnabled, "f");
__classPrivateFieldSet(this, _PPOMController_blockaidPublicKey, blockaidPublicKey, "f");
// add new network to chainStatus list
onNetworkChange(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onNetworkChange).bind(this));
// enable / disable PPOM validations as user changes preferences
onPreferencesChange(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onPreferenceChange).bind(this));
// register message handlers
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_registerMessageHandlers).call(this);
if (securityAlertsEnabled) {
// start scheduled task to fetch data files
if (__classPrivateFieldGet(this, _PPOMController_securityAlertsEnabled, "f")) {
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_scheduleFileDownloadForAllChains).call(this);

@@ -195,12 +183,9 @@ }

* This function will acquire mutex lock and invoke internal method #updatePPOM.
*
* @param options - Options.
* @param options.updateForAllChains - True is update if required to be done for all chains in cache.
*/
async updatePPOM({ updateForAllChains } = { updateForAllChains: true }) {
if (!this.state.securityAlertsEnabled) {
throw Error('User has not enabled blockaidSecurityCheck');
async updatePPOM() {
if (!__classPrivateFieldGet(this, _PPOMController_securityAlertsEnabled, "f")) {
throw Error('User has securityAlertsEnabled set to false');
}
await __classPrivateFieldGet(this, _PPOMController_ppomMutex, "f").use(async () => {
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updatePPOM).call(this, updateForAllChains);
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updatePPOM).call(this);
});

@@ -216,10 +201,10 @@ }

async usePPOM(callback) {
if (!this.state.securityAlertsEnabled) {
throw Error('User has not enabled blockaidSecurityCheck');
if (!__classPrivateFieldGet(this, _PPOMController_securityAlertsEnabled, "f")) {
throw Error('User has securityAlertsEnabled set to false');
}
return await __classPrivateFieldGet(this, _PPOMController_ppomMutex, "f").use(async () => {
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_resetPPOM).call(this);
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_maybeUpdatePPOM).call(this);
if (!__classPrivateFieldGet(this, _PPOMController_ppom, "f")) {
__classPrivateFieldSet(this, _PPOMController_ppom, await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getPPOM).call(this), "f");
}
__classPrivateFieldSet(this, _PPOMController_ppom, await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getPPOM).call(this), "f");
__classPrivateFieldSet(this, _PPOMController_providerRequests, 0, "f");
return await callback(__classPrivateFieldGet(this, _PPOMController_ppom, "f"));

@@ -230,5 +215,50 @@ });

exports.PPOMController = PPOMController;
_PPOMController_ppom = new WeakMap(), _PPOMController_provider = new WeakMap(), _PPOMController_storage = new WeakMap(), _PPOMController_refreshDataInterval = new WeakMap(), _PPOMController_fileScheduleInterval = new WeakMap(), _PPOMController_ppomMutex = new WeakMap(), _PPOMController_ppomProvider = new WeakMap(), _PPOMController_cdnBaseUrl = new WeakMap(), _PPOMController_instances = new WeakSet(), _PPOMController_registerMessageHandlers = function _PPOMController_registerMessageHandlers() {
_PPOMController_ppom = new WeakMap(), _PPOMController_provider = new WeakMap(), _PPOMController_storage = new WeakMap(), _PPOMController_refreshDataInterval = new WeakMap(), _PPOMController_fileScheduleInterval = new WeakMap(), _PPOMController_ppomMutex = new WeakMap(), _PPOMController_ppomProvider = new WeakMap(), _PPOMController_cdnBaseUrl = new WeakMap(), _PPOMController_providerRequestLimit = new WeakMap(), _PPOMController_providerRequests = new WeakMap(), _PPOMController_chainId = new WeakMap(), _PPOMController_dataUpdateDuration = new WeakMap(), _PPOMController_fileFetchScheduleDuration = new WeakMap(), _PPOMController_securityAlertsEnabled = new WeakMap(), _PPOMController_blockaidPublicKey = new WeakMap(), _PPOMController_instances = new WeakSet(), _PPOMController_onNetworkChange = function _PPOMController_onNetworkChange(networkControllerState) {
const id = networkControllerState.providerConfig.chainId;
if (id === __classPrivateFieldGet(this, _PPOMController_chainId, "f")) {
return;
}
let chainStatus = { ...this.state.chainStatus };
// delete ols chainId if total number of chainId is equal 5
const chainIds = Object.keys(chainStatus);
if (chainIds.length >= NETWORK_CACHE_LIMIT.MAX) {
const oldestChainId = chainIds.sort((c1, c2) => Number(chainStatus[c2]?.lastVisited) -
Number(chainStatus[c1]?.lastVisited))[NETWORK_CACHE_LIMIT.MAX - 1];
if (oldestChainId) {
delete chainStatus[oldestChainId];
}
}
const existingNetworkObject = chainStatus[id];
__classPrivateFieldSet(this, _PPOMController_chainId, id, "f");
chainStatus = {
...chainStatus,
[id]: {
lastVisited: new Date().getTime(),
dataFetched: existingNetworkObject?.dataFetched ?? false,
},
};
this.update((draftState) => {
draftState.chainStatus = chainStatus;
});
}, _PPOMController_onPreferenceChange = function _PPOMController_onPreferenceChange(preferenceControllerState) {
const blockaidEnabled = preferenceControllerState.securityAlertsEnabled;
if (blockaidEnabled === __classPrivateFieldGet(this, _PPOMController_securityAlertsEnabled, "f")) {
return;
}
if (blockaidEnabled) {
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_scheduleFileDownloadForAllChains).call(this);
}
else {
clearInterval(__classPrivateFieldGet(this, _PPOMController_refreshDataInterval, "f"));
clearInterval(__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f"));
}
__classPrivateFieldSet(this, _PPOMController_securityAlertsEnabled, blockaidEnabled, "f");
}, _PPOMController_registerMessageHandlers = function _PPOMController_registerMessageHandlers() {
this.messagingSystem.registerActionHandler(`${controllerName}:usePPOM`, this.usePPOM.bind(this));
this.messagingSystem.registerActionHandler(`${controllerName}:updatePPOM`, this.updatePPOM.bind(this));
}, _PPOMController_resetPPOM = function _PPOMController_resetPPOM() {
if (__classPrivateFieldGet(this, _PPOMController_ppom, "f")) {
__classPrivateFieldGet(this, _PPOMController_ppom, "f").free();
__classPrivateFieldSet(this, _PPOMController_ppom, undefined, "f");
}
}, _PPOMController_maybeUpdatePPOM =

@@ -242,44 +272,25 @@ /**

async function _PPOMController_maybeUpdatePPOM() {
if (__classPrivateFieldGet(this, _PPOMController_ppom, "f")) {
__classPrivateFieldGet(this, _PPOMController_ppom, "f").free();
__classPrivateFieldSet(this, _PPOMController_ppom, undefined, "f");
if (__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_isDataRequiredForCurrentChain).call(this)) {
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getNewFilesForCurrentChain).call(this);
}
if (await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_shouldUpdate).call(this)) {
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updatePPOM).call(this, false);
}
}, _PPOMController_shouldUpdate =
/**
* Determine if an update to the ppom configuration is needed.
* The function will return true if data is not already fetched for the chain.
*
* @returns True if PPOM data requires update.
*/
async function _PPOMController_shouldUpdate() {
const { chainId, chainStatus } = this.state;
return !chainStatus[chainId]?.dataFetched;
}, _PPOMController_isDataRequiredForCurrentChain = function _PPOMController_isDataRequiredForCurrentChain() {
const { chainStatus } = this.state;
return !chainStatus[__classPrivateFieldGet(this, _PPOMController_chainId, "f")]?.dataFetched;
}, _PPOMController_updatePPOM =
/**
* Update the PPOM configuration.
* This function will fetch the latest version info when needed, and update the PPOM storage.
*
* @param updateForAllChains - True if update is required to be done for all chains in chainStatus.
/*
* Update the PPOM configuration for all chainId.
*/
async function _PPOMController_updatePPOM(updateForAllChains) {
const versionInfoUpdated = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updateVersionInfo).call(this, updateForAllChains);
if (!versionInfoUpdated) {
return;
}
await __classPrivateFieldGet(this, _PPOMController_storage, "f").syncMetadata(this.state.versionInfo);
if (updateForAllChains) {
async function _PPOMController_updatePPOM() {
const versionInfoUpdated = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updateVersionInfo).call(this);
if (versionInfoUpdated) {
await __classPrivateFieldGet(this, _PPOMController_storage, "f").syncMetadata(this.state.versionInfo);
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getNewFilesForAllChains).call(this);
}
else {
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getNewFilesForCurrentChain).call(this);
}
}, _PPOMController_updateVersionInfo =
/*
* Fetch the version info from the CDN and update the version info in state.
* Function returns true if update is available for versionInfo.
*/
async function _PPOMController_updateVersionInfo(updateForAllChains) {
const versionInfo = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchVersionInfo).call(this, updateForAllChains);
async function _PPOMController_updateVersionInfo() {
const versionInfo = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchVersionInfo).call(this);
if (versionInfo) {

@@ -297,15 +308,22 @@ this.update((draftState) => {

file.checksum === fileVersionInfo.checksum);
}, _PPOMController_checkFilePath = function _PPOMController_checkFilePath(filePath) {
const filePathRegex = /^[\w./]+$/u;
if (!filePath.match(filePathRegex)) {
throw new Error(`Invalid file path for data file: ${filePath}`);
}
}, _PPOMController_getFile =
/**
/*
* Gets a single file from CDN and write to the storage.
*
* @param fileVersionInfo - Information about the file to be retrieved.
*/
async function _PPOMController_getFile(fileVersionInfo) {
const { storageMetadata } = this.state;
// do not fetch file if the storage version is latest
if (__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_checkFilePresentInStorage).call(this, storageMetadata, fileVersionInfo)) {
return;
}
// validate file path for valid characters
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_checkFilePath).call(this, fileVersionInfo.filePath);
const fileUrl = `${URL_PREFIX}${__classPrivateFieldGet(this, _PPOMController_cdnBaseUrl, "f")}/${fileVersionInfo.filePath}`;
const fileData = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchBlob).call(this, fileUrl);
await (0, util_1.validateSignature)(fileData, fileVersionInfo.signature, __classPrivateFieldGet(this, _PPOMController_blockaidPublicKey, "f"), fileVersionInfo.filePath);
await __classPrivateFieldGet(this, _PPOMController_storage, "f").writeFile({

@@ -327,14 +345,11 @@ data: fileData,

}, _PPOMController_getNewFilesForCurrentChain =
/**
* Fetches new files and save them to storage.
* The function is invoked if user if attempting transaction for a network,
/*
* Fetches new files for current network and save them to storage.
* The function is invoked if user if attempting transaction for current network,
* for which data is not previously fetched.
*
* @returns A promise that resolves to return void.
*/
async function _PPOMController_getNewFilesForCurrentChain() {
const { chainId, versionInfo } = this.state;
const { versionInfo } = this.state;
for (const fileVersionInfo of versionInfo) {
// download all files for the current chain.
if (fileVersionInfo.chainId !== chainId) {
if (fileVersionInfo.chainId !== __classPrivateFieldGet(this, _PPOMController_chainId, "f")) {
continue;

@@ -347,6 +362,7 @@ }

}
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, chainId);
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, __classPrivateFieldGet(this, _PPOMController_chainId, "f"));
}, _PPOMController_getListOfFilesToBeFetched = function _PPOMController_getListOfFilesToBeFetched() {
const { chainStatus, storageMetadata, versionInfo: stateVersionInfo, } = this.state;
// create a map of chainId and files belonging to that chainId
// not include the files for which the version in storage is the latest one
const chainIdsFileInfoList = Object.keys(chainStatus).map((chainId) => ({

@@ -374,6 +390,10 @@ chainId,

}, _PPOMController_deleteOldChainIds = function _PPOMController_deleteOldChainIds() {
// We keep minimum of 2 chainIds in the state
if (Object.keys(this.state.chainStatus)?.length <= NETWORK_CACHE_LIMIT.MIN) {
return;
}
const currentTimestamp = new Date().getTime();
const oldChaninIds = Object.keys(this.state.chainStatus).filter((chainId) => this.state.chainStatus[chainId].lastVisited <
currentTimestamp - exports.NETWORK_CACHE_DURATION &&
chainId !== this.state.chainId);
chainId !== __classPrivateFieldGet(this, _PPOMController_chainId, "f"));
const chainStatus = { ...this.state.chainStatus };

@@ -387,11 +407,11 @@ oldChaninIds.forEach((chainId) => {

}, _PPOMController_getNewFilesForAllChains =
/**
* Function that fetched and saves to storage files for all networks.
* Files are not fetched parallely but at an interval.
*
* @returns A promise that resolves to return void.
/*
* Function that fetches and saves to storage files for all networks.
* Files are not fetched parallely but at regular intervals to
* avoid sending a lot of parallel requests to CDN.
*/
async function _PPOMController_getNewFilesForAllChains() {
// delete chains more than a week old
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_deleteOldChainIds).call(this);
// clear already scheduled fetch if any
// clear existing scheduled task to fetch files if any
if (__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f")) {

@@ -402,11 +422,11 @@ clearInterval(__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f"));

const fileToBeFetchedList = __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getListOfFilesToBeFetched).call(this);
// if schedule interval is large so that not all files can be fetched in
// refreshInterval, reduce schedule interval
let scheduleInterval = this.state.fileScheduleInterval;
if (this.state.refreshInterval / (fileToBeFetchedList.length + 1) <
scheduleInterval) {
// Get scheduled interval, if schedule interval is large so that not all files can be fetched in
// this.#dataUpdateDuration, reduce schedule interval
let scheduleInterval = __classPrivateFieldGet(this, _PPOMController_fileFetchScheduleDuration, "f");
if (__classPrivateFieldGet(this, _PPOMController_dataUpdateDuration, "f") / (fileToBeFetchedList.length + 1) <
__classPrivateFieldGet(this, _PPOMController_fileFetchScheduleDuration, "f")) {
scheduleInterval =
this.state.refreshInterval / (fileToBeFetchedList.length + 1);
__classPrivateFieldGet(this, _PPOMController_dataUpdateDuration, "f") / (fileToBeFetchedList.length + 1);
}
// schedule files to be fetched in intervals
// schedule files to be fetched in regular intervals
__classPrivateFieldSet(this, _PPOMController_fileScheduleInterval, setInterval(() => {

@@ -417,12 +437,17 @@ const fileToBeFetched = fileToBeFetchedList.pop();

}
const { chainStatus } = this.state;
const { fileVersionInfo, isLastFileOfNetwork } = fileToBeFetched;
// get the file from CDN
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getFile).call(this, fileVersionInfo)
.then(() => {
if (isLastFileOfNetwork) {
// set dataFetched for chainId to true
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, fileVersionInfo.chainId);
}
})
.catch((exp) => console.error(`Error in getting file ${fileVersionInfo.filePath}: ${exp.message}`));
// check here if chain is present in chainStatus, it may be removed from chainStatus
// if more than 5 networks are added to it.
if (chainStatus[fileVersionInfo.chainId]) {
// get the file from CDN
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getFile).call(this, fileVersionInfo)
.then(() => {
if (isLastFileOfNetwork) {
// if this was last file for the chainId set dataFetched for chainId to true
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, fileVersionInfo.chainId);
}
})
.catch((exp) => console.error(`Error in getting file ${fileVersionInfo.filePath}: ${exp.message}`));
}
// clear interval if all files are fetched

@@ -435,15 +460,11 @@ if (!fileToBeFetchedList.length) {

/*
* getAPIResponse - Generic method to fetch file from CDN.
* Generic method to fetch file from CDN.
*/
async function _PPOMController_getAPIResponse(url, options = {}, method = 'GET') {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await (0, controller_utils_1.safelyExecute)(async () => fetch(url, {
const response = await (0, controller_utils_1.safelyExecute)(async () => (0, controller_utils_1.timeoutFetch)(url, {
method,
cache: 'no-cache',
redirect: 'error',
signal: controller.signal,
...options,
}), true);
clearTimeout(timeoutId);
}, 10000), true);
if (response?.status !== 200) {

@@ -453,2 +474,19 @@ throw new Error(`Failed to fetch file with url: ${url}`);

return response;
}, _PPOMController_checkIfVersionInfoETagChanged =
/*
* Function sends a HEAD request to version info file and compares the ETag to the one saved in controller state.
* If ETag is not changed we can be sure that there is not change in files and we do not need to fetch data again.
*/
async function _PPOMController_checkIfVersionInfoETagChanged(url) {
const headResponse = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getAPIResponse).call(this, url, {
headers: versionInfoFileHeaders,
}, 'HEAD');
const { versionFileETag } = this.state;
if (headResponse.headers.get('ETag') === versionFileETag) {
return false;
}
this.update((draftState) => {
draftState.versionFileETag = headResponse.headers.get('ETag');
});
return true;
}, _PPOMController_fetchVersionInfo =

@@ -458,15 +496,8 @@ /*

*/
async function _PPOMController_fetchVersionInfo(updateForAllChains) {
async function _PPOMController_fetchVersionInfo() {
const url = `${URL_PREFIX}${__classPrivateFieldGet(this, _PPOMController_cdnBaseUrl, "f")}/${PPOM_VERSION_FILE_NAME}`;
if (updateForAllChains) {
const headResponse = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getAPIResponse).call(this, url, {
headers: versionInfoFileHeaders,
}, 'HEAD');
const { versionFileETag } = this.state;
if (headResponse.headers.get('ETag') === versionFileETag) {
return undefined;
}
this.update((draftState) => {
draftState.versionFileETag = headResponse.headers.get('ETag');
});
// If ETag is same it is not required to fetch data files again
const eTagChanged = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_checkIfVersionInfoETagChanged).call(this, url);
if (!eTagChanged) {
return undefined;
}

@@ -479,3 +510,3 @@ const response = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getAPIResponse).call(this, url, {

/*
* Fetch the blob from the PPOM cdn.
* Fetch the blob file from the PPOM cdn.
*/

@@ -490,21 +521,17 @@ async function _PPOMController_fetchBlob(url) {

*/
async function _PPOMController_jsonRpcRequest(req) {
async function _PPOMController_jsonRpcRequest(method, params) {
return new Promise((resolve, reject) => {
const currentTimestamp = new Date().getTime();
const requests = this.state.providerRequests.filter((requestTime) => requestTime - currentTimestamp < FILE_FETCH_SCHEDULE_INTERVAL);
if (requests.length >= 5) {
reject(new Error('Number of request to provider from PPOM exceed rate limit'));
// Throw error if number of request to provider from PPOM exceed the limit for current transaction
if (__classPrivateFieldGet(this, _PPOMController_providerRequests, "f") > __classPrivateFieldGet(this, _PPOMController_providerRequestLimit, "f")) {
reject(util_1.PROVIDER_ERRORS.limitExceeded());
return;
}
this.update((draftState) => {
draftState.providerRequests = [
...this.state.providerRequests,
currentTimestamp,
];
});
if (!ALLOWED_PROVIDER_CALLS.includes(req.method)) {
reject(new Error(`Method not allowed on provider ${req.method}`));
__classPrivateFieldSet(this, _PPOMController_providerRequests, __classPrivateFieldGet(this, _PPOMController_providerRequests, "f") + 1, "f");
// Throw error if the method called on provider by PPOM is not allowed for PPOM
if (!ALLOWED_PROVIDER_CALLS.includes(method)) {
reject(util_1.PROVIDER_ERRORS.methodNotSupported());
return;
}
__classPrivateFieldGet(this, _PPOMController_provider, "f").sendAsync(req, (error, res) => {
// Invoke provider and return result
__classPrivateFieldGet(this, _PPOMController_provider, "f").sendAsync((0, util_1.createPayload)(method, params), (error, res) => {
if (error) {

@@ -520,12 +547,11 @@ reject(error);

/*
* Initialize the PPOM.
* This function will be called when the PPOM is first used.
* or when the PPOM is out of date.
* It will load the PPOM data from storage and initialize the PPOM.
* This function can be called to initialise PPOM or re-initilise it,
* when new files are required to be passed to it.
*
* It will load the data files from storage and pass data files and wasm file to ppom.
*/
async function _PPOMController_getPPOM() {
const { ppomInit, PPOM } = __classPrivateFieldGet(this, _PPOMController_ppomProvider, "f");
const { chainId } = this.state;
// Get all the files for the chainId
const files = await Promise.all(this.state.versionInfo
.filter((file) => file.chainId === chainId)
.filter((file) => file.chainId === __classPrivateFieldGet(this, _PPOMController_chainId, "f"))
.map(async (file) => {

@@ -535,5 +561,13 @@ const data = await __classPrivateFieldGet(this, _PPOMController_storage, "f").readFile(file.name, file.chainId);

}));
// The following code throw error if no data files are found for the chainId.
// This check has been put in place after suggestion of security team.
// If we want to disable ppom validation on all instances of Metamask,
// this can be achieved by returning empty data from version file.
if (!files.length) {
throw new Error(`Aborting validation as no files are found for the network with chainId: ${__classPrivateFieldGet(this, _PPOMController_chainId, "f")}`);
}
const { ppomInit, PPOM } = __classPrivateFieldGet(this, _PPOMController_ppomProvider, "f");
await ppomInit('./ppom_bg.wasm');
return PPOM.new(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_jsonRpcRequest).bind(this), files);
}, _PPOMController_onFileScheduledInterval = function _PPOMController_onFileScheduledInterval() {
}, _PPOMController_onDataUpdateDuration = function _PPOMController_onDataUpdateDuration() {
this.updatePPOM().catch(() => {

@@ -543,6 +577,5 @@ // console.error(`Error while trying to update PPOM: ${exp.message}`);

}, _PPOMController_scheduleFileDownloadForAllChains = function _PPOMController_scheduleFileDownloadForAllChains() {
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onFileScheduledInterval).call(this);
__classPrivateFieldSet(this, _PPOMController_refreshDataInterval, setInterval(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onFileScheduledInterval).bind(this), this.state.refreshInterval), "f");
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onDataUpdateDuration).call(this);
__classPrivateFieldSet(this, _PPOMController_refreshDataInterval, setInterval(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_onDataUpdateDuration).bind(this), __classPrivateFieldGet(this, _PPOMController_dataUpdateDuration, "f")), "f");
};
// todo: handle empty version info file to hold on validations
//# sourceMappingURL=ppom-controller.js.map
{
"name": "@metamask/ppom-validator",
"version": "0.1.2",
"version": "0.2.0",
"description": "This module has code to integrate Blockaid PPOM with MetaMask",

@@ -36,3 +36,5 @@ "homepage": "https://github.com/MetaMask/ppom-validator#readme",

"@metamask/controller-utils": "^4.0.0",
"await-semaphore": "^0.1.3"
"await-semaphore": "^0.1.3",
"elliptic": "^6.5.4",
"json-rpc-random-id": "^1.0.1"
},

@@ -47,3 +49,5 @@ "devDependencies": {

"@metamask/eslint-config-typescript": "^11.0.0",
"@types/elliptic": "^6.4.14",
"@types/jest": "^28.1.6",
"@types/json-rpc-random-id": "^1.0.1",
"@types/node": "^16",

@@ -50,0 +54,0 @@ "@typescript-eslint/eslint-plugin": "^5.43.0",

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc