@metamask/ppom-validator
Advanced tools
Comparing version 0.0.1 to 0.1.1
@@ -9,2 +9,9 @@ # Changelog | ||
## [0.1.1] | ||
### Added | ||
- Improvements in CDN data fetching ([#15](https://github.com/MetaMask/ppom-validator/pull/15)) | ||
- Mobile integration ([#14](https://github.com/MetaMask/ppom-validator/pull/14)) | ||
- Caching data for multiple networks ([#10](https://github.com/MetaMask/ppom-validator/pull/10)) | ||
- Adding periodic sync for ppom data ([#6](https://github.com/MetaMask/ppom-validator/pull/6)) | ||
## [0.0.1] | ||
@@ -22,3 +29,4 @@ ### Added | ||
[Unreleased]: https://github.com/MetaMask/ppom-validator/compare/v0.0.1...HEAD | ||
[Unreleased]: https://github.com/MetaMask/ppom-validator/compare/v0.1.1...HEAD | ||
[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 |
export * from './ppom-controller'; | ||
export type { StorageBackend, StorageKey } from './ppom-storage'; | ||
export * from './ppom-middleware'; |
@@ -18,3 +18,2 @@ "use strict"; | ||
__exportStar(require("./ppom-controller"), exports); | ||
__exportStar(require("./ppom-middleware"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -1,5 +0,5 @@ | ||
import * as PPOMModule from '@blockaid/ppom-mock'; | ||
import { BaseControllerV2, RestrictedControllerMessenger } from '@metamask/base-controller'; | ||
import { StorageBackend, FileMetadataList, FileMetadata } from './ppom-storage'; | ||
export declare const DAY_IN_MILLISECONDS: number; | ||
export declare const REFRESH_TIME_INTERVAL: number; | ||
export declare const NETWORK_CACHE_DURATION: number; | ||
/** | ||
@@ -18,8 +18,7 @@ * @type PPOMFileVersion | ||
/** | ||
* @type PPOMControllerState | ||
* @type PPOMState | ||
* | ||
* Controller state | ||
* @property lastFetched - Time when files were last updated. | ||
* @property lastChainId - ChainId for which files were last updated. | ||
* @property newChainId - ChainIf of currently selected network. | ||
* @property chainId - ID of current chain. | ||
* @property chainStatus - Array of chainId and time it was last visited. | ||
* @property versionInfo - Version information fetched from CDN. | ||
@@ -30,25 +29,23 @@ * @property storageMetadata - Metadata of files storaged in storage. | ||
*/ | ||
export declare type PPOMControllerState = { | ||
lastFetched: number; | ||
lastChainId: string; | ||
newChainId: string; | ||
export declare type PPOMState = { | ||
chainId: string; | ||
chainStatus: Record<string, { | ||
chainId: string; | ||
lastVisited: number; | ||
dataFetched: boolean; | ||
}>; | ||
versionInfo: PPOMVersionResponse; | ||
storageMetadata: FileMetadataList; | ||
refreshInterval: number; | ||
fileScheduleInterval: number; | ||
providerRequestLimit: number; | ||
providerRequests: number[]; | ||
securityAlertsEnabled: boolean; | ||
versionFileETag?: string; | ||
}; | ||
declare const controllerName = "PPOMController"; | ||
export declare type Clear = { | ||
type: `${typeof controllerName}:clear`; | ||
handler: () => void; | ||
}; | ||
export declare type UsePPOM = { | ||
type: `${typeof controllerName}:usePPOM`; | ||
handler: (callback: (ppom: PPOMModule.PPOM) => Promise<any>) => Promise<any>; | ||
handler: (callback: (ppom: any) => Promise<any>) => Promise<any>; | ||
}; | ||
export declare type SetRefreshInterval = { | ||
type: `${typeof controllerName}:setRefreshInterval`; | ||
handler: (interval: number) => void; | ||
}; | ||
export declare type UpdatePPOM = { | ||
@@ -58,4 +55,8 @@ type: `${typeof controllerName}:updatePPOM`; | ||
}; | ||
export declare type PPOMControllerActions = Clear | UsePPOM | SetRefreshInterval | UpdatePPOM; | ||
export declare type PPOMControllerActions = UsePPOM | UpdatePPOM; | ||
export declare type PPOMControllerMessenger = RestrictedControllerMessenger<typeof controllerName, PPOMControllerActions, never, never, never>; | ||
declare type PPOMProvider = { | ||
ppomInit: () => Promise<void>; | ||
PPOM: any; | ||
}; | ||
/** | ||
@@ -71,3 +72,3 @@ * PPOMController | ||
*/ | ||
export declare class PPOMController extends BaseControllerV2<typeof controllerName, PPOMControllerState, PPOMControllerMessenger> { | ||
export declare class PPOMController extends BaseControllerV2<typeof controllerName, PPOMState, PPOMControllerMessenger> { | ||
#private; | ||
@@ -78,36 +79,37 @@ /** | ||
* @param options - Constructor options. | ||
* @param options.chainId - Id of current chain. | ||
* @param options.chainId - ChainId of the selected network. | ||
* @param options.messenger - Controller messenger. | ||
* @param options.onNetworkChange - Callback tobe invoked when network changes. | ||
* @param options.provider - The provider used to create the PPOM instance. | ||
* @param options.state - The controller state. | ||
* @param options.storageBackend - The storage backend to use for storing PPOM data. | ||
* @param options.securityAlertsEnabled - True if user has enabled preference for blockaid security check. | ||
* @param options.onPreferencesChange - Callback invoked when user changes preferences. | ||
* @param options.ppomProvider - Object wrapping PPOM. | ||
* @param options.cdnBaseUrl - Base URL for the CDN. | ||
* @param options.state - Initial state of the controller. | ||
* @returns The PPOMController instance. | ||
*/ | ||
constructor({ chainId, messenger, onNetworkChange, provider, state, storageBackend, }: { | ||
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, state, }: { | ||
chainId: string; | ||
onNetworkChange: (callback: (networkState: any) => void) => void; | ||
messenger: PPOMControllerMessenger; | ||
onNetworkChange: (callback: (chainId: string) => void) => void; | ||
provider: any; | ||
state?: PPOMControllerState; | ||
storageBackend: StorageBackend; | ||
securityAlertsEnabled: boolean; | ||
onPreferencesChange: (callback: (perferenceState: any) => void) => void; | ||
ppomProvider: PPOMProvider; | ||
cdnBaseUrl: string; | ||
state?: PPOMState; | ||
}); | ||
/** | ||
* Clear the controller state. | ||
*/ | ||
clear(): void; | ||
/** | ||
* Set the interval at which the ppom version info will be fetched. | ||
* Fetching will only occur on the next call to test/bypass. | ||
* For immediate update to the ppom lists, call updatePPOM directly. | ||
* Update the PPOM. | ||
* This function will acquire mutex lock and invoke internal method #updatePPOM. | ||
* | ||
* @param interval - The new interval in ms. | ||
* @param options - Options. | ||
* @param options.updateForAllChains - True is update if required to be done for all chains in cache. | ||
*/ | ||
setRefreshInterval(interval: number): void; | ||
updatePPOM({ updateForAllChains }?: { | ||
updateForAllChains: boolean; | ||
}): Promise<void>; | ||
/** | ||
* Update the PPOM configuration. | ||
* This function will fetch the latest version info when needed, and update the PPOM storage. | ||
*/ | ||
updatePPOM(): Promise<void>; | ||
/** | ||
* Use the PPOM. | ||
@@ -119,4 +121,4 @@ * This function receives a callback that will be called with the PPOM. | ||
*/ | ||
usePPOM<T>(callback: (ppom: PPOMModule.PPOM) => Promise<T>): Promise<T>; | ||
usePPOM<T>(callback: (ppom: any) => Promise<T>): Promise<T>; | ||
} | ||
export {}; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
@@ -36,6 +13,5 @@ if (kind === "m") throw new TypeError("Private method is not writable"); | ||
}; | ||
var _PPOMController_instances, _PPOMController_ppom, _PPOMController_provider, _PPOMController_storage, _PPOMController_ppomMutex, _PPOMController_defaultState, _PPOMController_registerMessageHandlers, _PPOMController_shouldUpdate, _PPOMController_isOutOfDate, _PPOMController_getNewFiles, _PPOMController_updateVersionInfo, _PPOMController_maybeUpdatePPOM, _PPOMController_fetchBlob, _PPOMController_fetchVersionInfo, _PPOMController_jsonRpcRequest, _PPOMController_getPPOM; | ||
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; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.PPOMController = exports.DAY_IN_MILLISECONDS = void 0; | ||
const PPOMModule = __importStar(require("@blockaid/ppom-mock")); | ||
exports.PPOMController = exports.NETWORK_CACHE_DURATION = exports.REFRESH_TIME_INTERVAL = void 0; | ||
const base_controller_1 = require("@metamask/base-controller"); | ||
@@ -45,5 +21,6 @@ const controller_utils_1 = require("@metamask/controller-utils"); | ||
const ppom_storage_1 = require("./ppom-storage"); | ||
exports.DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24; | ||
exports.REFRESH_TIME_INTERVAL = 1000 * 60 * 60 * 2; | ||
const PROVIDER_REQUEST_LIMIT = 500; | ||
const MILLISECONDS_IN_FIVE_MINUTES = 300000; | ||
const FILE_FETCH_SCHEDULE_INTERVAL = 1000 * 60 * 5; | ||
exports.NETWORK_CACHE_DURATION = 1000 * 60 * 60 * 24 * 7; | ||
// The following methods on provider are allowed to PPOM | ||
@@ -69,16 +46,22 @@ const ALLOWED_PROVIDER_CALLS = [ | ||
const stateMetaData = { | ||
lastFetched: { persist: false, anonymous: false }, | ||
lastChainId: { persist: false, anonymous: false }, | ||
newChainId: { persist: false, anonymous: false }, | ||
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 }, | ||
}; | ||
// TODO: replace with metamask cdn | ||
const PPOM_CDN_BASE_URL = 'https://storage.googleapis.com/ppom-cdn/'; | ||
const PPOM_VERSION = 'ppom_version.json'; | ||
const PPOM_VERSION_PATH = `${PPOM_CDN_BASE_URL}${PPOM_VERSION}`; | ||
const PPOM_VERSION_FILE_NAME = 'ppom_version.json'; | ||
const URL_PREFIX = 'https://'; | ||
const controllerName = 'PPOMController'; | ||
const versionInfoFileHeaders = { | ||
headers: { | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
'Content-Type': 'application/json', | ||
}, | ||
}; | ||
/** | ||
@@ -99,21 +82,31 @@ * PPOMController | ||
* @param options - Constructor options. | ||
* @param options.chainId - Id of current chain. | ||
* @param options.chainId - ChainId of the selected network. | ||
* @param options.messenger - Controller messenger. | ||
* @param options.onNetworkChange - Callback tobe invoked when network changes. | ||
* @param options.provider - The provider used to create the PPOM instance. | ||
* @param options.state - The controller state. | ||
* @param options.storageBackend - The storage backend to use for storing PPOM data. | ||
* @param options.securityAlertsEnabled - True if user has enabled preference for blockaid security check. | ||
* @param options.onPreferencesChange - Callback invoked when user changes preferences. | ||
* @param options.ppomProvider - Object wrapping PPOM. | ||
* @param options.cdnBaseUrl - Base URL for the CDN. | ||
* @param options.state - Initial state of the controller. | ||
* @returns The PPOMController instance. | ||
*/ | ||
constructor({ chainId, messenger, onNetworkChange, provider, state, storageBackend, }) { | ||
const defaultState = { | ||
lastFetched: 0, | ||
versionInfo: [], | ||
storageMetadata: [], | ||
lastChainId: '', | ||
newChainId: chainId, | ||
refreshInterval: exports.DAY_IN_MILLISECONDS, | ||
providerRequestLimit: PROVIDER_REQUEST_LIMIT, | ||
providerRequests: [], | ||
...state, | ||
constructor({ chainId, messenger, onNetworkChange, provider, storageBackend, securityAlertsEnabled, onPreferencesChange, ppomProvider, cdnBaseUrl, state, }) { | ||
const initialState = { | ||
versionInfo: state?.versionInfo ?? [], | ||
storageMetadata: state?.storageMetadata ?? [], | ||
chainId, | ||
chainStatus: state?.chainStatus ?? { | ||
[chainId]: { | ||
chainId, | ||
lastVisited: new Date().getTime(), | ||
dataFetched: false, | ||
}, | ||
}, | ||
refreshInterval: state?.refreshInterval ?? exports.REFRESH_TIME_INTERVAL, | ||
fileScheduleInterval: state?.fileScheduleInterval ?? FILE_FETCH_SCHEDULE_INTERVAL, | ||
providerRequestLimit: state?.providerRequestLimit ?? PROVIDER_REQUEST_LIMIT, | ||
providerRequests: state?.providerRequests ?? [], | ||
securityAlertsEnabled, | ||
}; | ||
@@ -124,3 +117,3 @@ super({ | ||
messenger, | ||
state: { ...defaultState, ...state }, | ||
state: initialState, | ||
}); | ||
@@ -131,2 +124,4 @@ _PPOMController_instances.add(this); | ||
_PPOMController_storage.set(this, void 0); | ||
_PPOMController_refreshDataInterval.set(this, void 0); | ||
_PPOMController_fileScheduleInterval.set(this, void 0); | ||
/* | ||
@@ -137,5 +132,6 @@ * This mutex is used to prevent concurrent usage of the PPOM instance | ||
_PPOMController_ppomMutex.set(this, void 0); | ||
_PPOMController_defaultState.set(this, void 0); | ||
__classPrivateFieldSet(this, _PPOMController_defaultState, defaultState, "f"); | ||
_PPOMController_ppomProvider.set(this, void 0); | ||
_PPOMController_cdnBaseUrl.set(this, void 0); | ||
__classPrivateFieldSet(this, _PPOMController_provider, provider, "f"); | ||
__classPrivateFieldSet(this, _PPOMController_ppomProvider, ppomProvider, "f"); | ||
__classPrivateFieldSet(this, _PPOMController_storage, new ppom_storage_1.PPOMStorage({ | ||
@@ -153,47 +149,58 @@ storageBackend, | ||
__classPrivateFieldSet(this, _PPOMController_ppomMutex, new await_semaphore_1.Mutex(), "f"); | ||
onNetworkChange((id) => { | ||
__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.newChainId = id; | ||
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; | ||
}); | ||
}); | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_registerMessageHandlers).call(this); | ||
if (securityAlertsEnabled) { | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_scheduleFileDownloadForAllChains).call(this); | ||
} | ||
} | ||
/** | ||
* Clear the controller state. | ||
*/ | ||
clear() { | ||
this.update(() => __classPrivateFieldGet(this, _PPOMController_defaultState, "f")); | ||
} | ||
/** | ||
* Set the interval at which the ppom version info will be fetched. | ||
* Fetching will only occur on the next call to test/bypass. | ||
* For immediate update to the ppom lists, call updatePPOM directly. | ||
* Update the PPOM. | ||
* This function will acquire mutex lock and invoke internal method #updatePPOM. | ||
* | ||
* @param interval - The new interval in ms. | ||
* @param options - Options. | ||
* @param options.updateForAllChains - True is update if required to be done for all chains in cache. | ||
*/ | ||
setRefreshInterval(interval) { | ||
this.update((draftState) => { | ||
draftState.refreshInterval = interval; | ||
}); | ||
} | ||
/** | ||
* Update the PPOM configuration. | ||
* This function will fetch the latest version info when needed, and update the PPOM storage. | ||
*/ | ||
async updatePPOM() { | ||
if (__classPrivateFieldGet(this, _PPOMController_ppom, "f")) { | ||
__classPrivateFieldGet(this, _PPOMController_ppom, "f").free(); | ||
__classPrivateFieldSet(this, _PPOMController_ppom, undefined, "f"); | ||
async updatePPOM({ updateForAllChains } = { updateForAllChains: true }) { | ||
if (!this.state.securityAlertsEnabled) { | ||
throw Error('User has not enabled blockaidSecurityCheck'); | ||
} | ||
if (__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_isOutOfDate).call(this)) { | ||
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updateVersionInfo).call(this); | ||
} | ||
this.update((draftState) => { | ||
draftState.lastChainId = this.state.newChainId; | ||
await __classPrivateFieldGet(this, _PPOMController_ppomMutex, "f").use(async () => { | ||
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_updatePPOM).call(this, updateForAllChains); | ||
}); | ||
const storageMetadata = await __classPrivateFieldGet(this, _PPOMController_storage, "f").syncMetadata(this.state.versionInfo); | ||
const newFiles = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getNewFiles).call(this, this.state.newChainId, storageMetadata); | ||
for (const file of newFiles) { | ||
await __classPrivateFieldGet(this, _PPOMController_storage, "f").writeFile(file); | ||
} | ||
} | ||
@@ -208,2 +215,5 @@ /** | ||
async usePPOM(callback) { | ||
if (!this.state.securityAlertsEnabled) { | ||
throw Error('User has not enabled blockaidSecurityCheck'); | ||
} | ||
return await __classPrivateFieldGet(this, _PPOMController_ppomMutex, "f").use(async () => { | ||
@@ -219,14 +229,24 @@ await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_maybeUpdatePPOM).call(this); | ||
exports.PPOMController = PPOMController; | ||
_PPOMController_ppom = new WeakMap(), _PPOMController_provider = new WeakMap(), _PPOMController_storage = new WeakMap(), _PPOMController_ppomMutex = new WeakMap(), _PPOMController_defaultState = new WeakMap(), _PPOMController_instances = new WeakSet(), _PPOMController_registerMessageHandlers = function _PPOMController_registerMessageHandlers() { | ||
this.messagingSystem.registerActionHandler(`${controllerName}:clear`, this.clear.bind(this)); | ||
_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() { | ||
this.messagingSystem.registerActionHandler(`${controllerName}:usePPOM`, this.usePPOM.bind(this)); | ||
this.messagingSystem.registerActionHandler(`${controllerName}:setRefreshInterval`, this.setRefreshInterval.bind(this)); | ||
this.messagingSystem.registerActionHandler(`${controllerName}:updatePPOM`, this.updatePPOM.bind(this)); | ||
}, _PPOMController_maybeUpdatePPOM = | ||
/** | ||
* Conditionally update the ppom configuration. | ||
* | ||
* If the ppom configuration is out of date, this function will call `updatePPOM` | ||
* to update the configuration. | ||
*/ | ||
async function _PPOMController_maybeUpdatePPOM() { | ||
if (__classPrivateFieldGet(this, _PPOMController_ppom, "f")) { | ||
__classPrivateFieldGet(this, _PPOMController_ppom, "f").free(); | ||
__classPrivateFieldSet(this, _PPOMController_ppom, undefined, "f"); | ||
} | ||
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 | ||
* - the chainId has changed | ||
* - the ppom is out of date | ||
* - the ppom is not initialized. | ||
* The function will return true if data is not already fetched for the chain. | ||
* | ||
@@ -236,75 +256,189 @@ * @returns True if PPOM data requires update. | ||
async function _PPOMController_shouldUpdate() { | ||
if (this.state.newChainId !== this.state.lastChainId || | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_isOutOfDate).call(this)) { | ||
const { chainId, chainStatus } = this.state; | ||
return !chainStatus[chainId]?.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. | ||
*/ | ||
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) { | ||
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. | ||
*/ | ||
async function _PPOMController_updateVersionInfo(updateForAllChains) { | ||
const versionInfo = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchVersionInfo).call(this, updateForAllChains); | ||
if (versionInfo) { | ||
this.update((draftState) => { | ||
draftState.versionInfo = versionInfo; | ||
}); | ||
return true; | ||
} | ||
return __classPrivateFieldGet(this, _PPOMController_ppom, "f") === undefined; | ||
}, _PPOMController_isOutOfDate = function _PPOMController_isOutOfDate() { | ||
return Date.now() - this.state.lastFetched >= this.state.refreshInterval; | ||
}, _PPOMController_getNewFiles = | ||
return false; | ||
}, _PPOMController_checkFilePresentInStorage = function _PPOMController_checkFilePresentInStorage(storageMetadata, fileVersionInfo) { | ||
return storageMetadata.find((file) => file.name === fileVersionInfo.name && | ||
file.chainId === fileVersionInfo.chainId && | ||
file.version === fileVersionInfo.version && | ||
file.checksum === fileVersionInfo.checksum); | ||
}, _PPOMController_getFile = | ||
/** | ||
* Returns an array of new files that should be downloaded and saved to storage. | ||
* Gets a single file from CDN and write to the storage. | ||
* | ||
* @param chainId - The chain ID to check for files. | ||
* @param storageMetadata - An array of file metadata objects already in storage. | ||
* @returns A promise that resolves to an array of new files to download and save to storage. | ||
* @param fileVersionInfo - Information about the file to be retrieved. | ||
*/ | ||
async function _PPOMController_getNewFiles(chainId, storageMetadata) { | ||
const newFiles = []; | ||
for (const fileVersionInfo of this.state.versionInfo) { | ||
// download all files for the current chain + generally required files. | ||
if (fileVersionInfo.chainId && fileVersionInfo.chainId !== chainId) { | ||
async function _PPOMController_getFile(fileVersionInfo) { | ||
const { storageMetadata } = this.state; | ||
if (__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_checkFilePresentInStorage).call(this, storageMetadata, fileVersionInfo)) { | ||
return; | ||
} | ||
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 __classPrivateFieldGet(this, _PPOMController_storage, "f").writeFile({ | ||
data: fileData, | ||
...fileVersionInfo, | ||
}); | ||
}, _PPOMController_setChainIdDataFetched = function _PPOMController_setChainIdDataFetched(chainId) { | ||
const { chainStatus } = this.state; | ||
const chainIdObject = chainStatus[chainId]; | ||
if (chainIdObject && !chainIdObject.dataFetched) { | ||
this.update((draftState) => { | ||
draftState.chainStatus = { | ||
...chainStatus, | ||
[chainId]: { ...chainIdObject, dataFetched: true }, | ||
}; | ||
}); | ||
} | ||
}, _PPOMController_getNewFilesForCurrentChain = | ||
/** | ||
* Fetches new files and save them to storage. | ||
* The function is invoked if user if attempting transaction for a 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; | ||
for (const fileVersionInfo of versionInfo) { | ||
// download all files for the current chain. | ||
if (fileVersionInfo.chainId !== chainId) { | ||
continue; | ||
} | ||
// check if file is already in storage | ||
if (storageMetadata.find((file) => file.name === fileVersionInfo.name && | ||
file.chainId === fileVersionInfo.chainId && | ||
file.version === fileVersionInfo.version && | ||
file.checksum === fileVersionInfo.checksum)) { | ||
continue; | ||
} | ||
const fileUrl = `${PPOM_CDN_BASE_URL}${fileVersionInfo.filePath}`; | ||
const fileData = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchBlob).call(this, fileUrl); | ||
newFiles.push({ | ||
data: fileData, | ||
...fileVersionInfo, | ||
await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getFile).call(this, fileVersionInfo).catch((exp) => { | ||
console.error(`Error in getting file ${fileVersionInfo.filePath}: ${exp.message}`); | ||
throw exp; | ||
}); | ||
} | ||
return newFiles; | ||
}, _PPOMController_updateVersionInfo = | ||
/* | ||
* Fetch the version info from the PPOM cdn. | ||
* update the version info in state. | ||
*/ | ||
async function _PPOMController_updateVersionInfo() { | ||
const versionInfo = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_fetchVersionInfo).call(this, PPOM_VERSION_PATH); | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, chainId); | ||
}, _PPOMController_getListOfFilesToBeFetched = function _PPOMController_getListOfFilesToBeFetched() { | ||
const { chainStatus, storageMetadata, versionInfo: stateVersionInfo, } = this.state; | ||
// create a map of chainId and files belonging to that chainId | ||
const chainIdsFileInfoList = Object.keys(chainStatus).map((chainId) => ({ | ||
chainId, | ||
versionInfo: stateVersionInfo.filter((versionInfo) => versionInfo.chainId === chainId && | ||
!__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_checkFilePresentInStorage).call(this, storageMetadata, versionInfo)), | ||
})); | ||
// build a list of files to be fetched for all networks | ||
const fileToBeFetchedList = []; | ||
chainIdsFileInfoList.forEach((chainIdFileInfo) => { | ||
const { chainId, versionInfo } = chainIdFileInfo; | ||
versionInfo.forEach((fileVersionInfo, index) => { | ||
fileToBeFetchedList.push({ | ||
fileVersionInfo, | ||
isLastFileOfNetwork: index === versionInfo.length - 1, | ||
}); | ||
}); | ||
if (versionInfo.length === 0) { | ||
// set dataFetched to true for chainId | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_setChainIdDataFetched).call(this, chainId); | ||
} | ||
}); | ||
return fileToBeFetchedList; | ||
}, _PPOMController_deleteOldChainIds = function _PPOMController_deleteOldChainIds() { | ||
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); | ||
const chainStatus = { ...this.state.chainStatus }; | ||
oldChaninIds.forEach((chainId) => { | ||
delete chainStatus[chainId]; | ||
}); | ||
this.update((draftState) => { | ||
draftState.versionInfo = versionInfo; | ||
draftState.lastFetched = Date.now(); | ||
draftState.chainStatus = chainStatus; | ||
}); | ||
}, _PPOMController_maybeUpdatePPOM = | ||
}, _PPOMController_getNewFilesForAllChains = | ||
/** | ||
* Conditionally update the ppom configuration. | ||
* Function that fetched and saves to storage files for all networks. | ||
* Files are not fetched parallely but at an interval. | ||
* | ||
* If the ppom configuration is out of date, this function will call `updatePPOM` | ||
* to update the configuration. | ||
* @returns A promise that resolves to return void. | ||
*/ | ||
async function _PPOMController_maybeUpdatePPOM() { | ||
if (await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_shouldUpdate).call(this)) { | ||
await this.updatePPOM(); | ||
async function _PPOMController_getNewFilesForAllChains() { | ||
__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_deleteOldChainIds).call(this); | ||
// clear already scheduled fetch if any | ||
if (__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f")) { | ||
clearInterval(__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f")); | ||
} | ||
}, _PPOMController_fetchBlob = | ||
// build a list of files to be fetched for all networks | ||
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) { | ||
scheduleInterval = | ||
this.state.refreshInterval / (fileToBeFetchedList.length + 1); | ||
} | ||
// schedule files to be fetched in intervals | ||
__classPrivateFieldSet(this, _PPOMController_fileScheduleInterval, setInterval(() => { | ||
const fileToBeFetched = fileToBeFetchedList.pop(); | ||
if (!fileToBeFetched) { | ||
return; | ||
} | ||
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}`)); | ||
// clear interval if all files are fetched | ||
if (!fileToBeFetchedList.length) { | ||
clearInterval(__classPrivateFieldGet(this, _PPOMController_fileScheduleInterval, "f")); | ||
} | ||
}, scheduleInterval), "f"); | ||
}, _PPOMController_getAPIResponse = | ||
/* | ||
* Fetch the blob from the PPOM cdn. | ||
* getAPIResponse - Generic method to fetch file from CDN. | ||
*/ | ||
async function _PPOMController_fetchBlob(fileUrl) { | ||
const response = await (0, controller_utils_1.safelyExecute)(async () => fetch(fileUrl, { cache: 'no-cache' }), true); | ||
switch (response?.status) { | ||
case 200: { | ||
return await response.arrayBuffer(); | ||
} | ||
default: { | ||
throw new Error(`Failed to fetch file with url ${fileUrl}`); | ||
} | ||
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, { | ||
method, | ||
cache: 'no-cache', | ||
redirect: 'error', | ||
signal: controller.signal, | ||
...options, | ||
}), true); | ||
clearTimeout(timeoutId); | ||
if (response?.status !== 200) { | ||
throw new Error(`Failed to fetch file with url: ${url}`); | ||
} | ||
return response; | ||
}, _PPOMController_fetchVersionInfo = | ||
@@ -314,12 +448,27 @@ /* | ||
*/ | ||
async function _PPOMController_fetchVersionInfo(url) { | ||
const response = await (0, controller_utils_1.safelyExecute)(async () => fetch(url, { cache: 'no-cache' }), true); | ||
switch (response?.status) { | ||
case 200: { | ||
return response.json(); | ||
async function _PPOMController_fetchVersionInfo(updateForAllChains) { | ||
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; | ||
} | ||
default: { | ||
throw new Error(`Failed to fetch version info url: ${url}`); | ||
} | ||
this.update((draftState) => { | ||
draftState.versionFileETag = headResponse.headers.get('ETag'); | ||
}); | ||
} | ||
const response = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getAPIResponse).call(this, url, { | ||
headers: versionInfoFileHeaders, | ||
}); | ||
return response.json(); | ||
}, _PPOMController_fetchBlob = | ||
/* | ||
* Fetch the blob from the PPOM cdn. | ||
*/ | ||
async function _PPOMController_fetchBlob(url) { | ||
const response = await __classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_getAPIResponse).call(this, url); | ||
return await response.arrayBuffer(); | ||
}, _PPOMController_jsonRpcRequest = | ||
@@ -333,3 +482,3 @@ /* | ||
const currentTimestamp = new Date().getTime(); | ||
const requests = this.state.providerRequests.filter((requestTime) => requestTime - currentTimestamp < MILLISECONDS_IN_FIVE_MINUTES); | ||
const requests = this.state.providerRequests.filter((requestTime) => requestTime - currentTimestamp < FILE_FETCH_SCHEDULE_INTERVAL); | ||
if (requests.length >= 5) { | ||
@@ -366,6 +515,7 @@ reject(new Error('Number of request to provider from PPOM exceed rate limit')); | ||
async function _PPOMController_getPPOM() { | ||
await PPOMModule.default(); | ||
const chainId = this.state.lastChainId; | ||
const { ppomInit, PPOM } = __classPrivateFieldGet(this, _PPOMController_ppomProvider, "f"); | ||
await ppomInit(); | ||
const { chainId } = this.state; | ||
const files = await Promise.all(this.state.versionInfo | ||
.filter((file) => !file.chainId || file.chainId === chainId) | ||
.filter((file) => file.chainId === chainId) | ||
.map(async (file) => { | ||
@@ -375,4 +525,12 @@ const data = await __classPrivateFieldGet(this, _PPOMController_storage, "f").readFile(file.name, file.chainId); | ||
})); | ||
return new PPOMModule.PPOM(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_jsonRpcRequest).bind(this), files); | ||
return new PPOM(__classPrivateFieldGet(this, _PPOMController_instances, "m", _PPOMController_jsonRpcRequest).bind(this), files); | ||
}, _PPOMController_onFileScheduledInterval = function _PPOMController_onFileScheduledInterval() { | ||
this.updatePPOM().catch(() => { | ||
// 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"); | ||
}; | ||
// todo: handle empty version info file to hold on validations | ||
//# sourceMappingURL=ppom-controller.js.map |
{ | ||
"name": "@metamask/ppom-validator", | ||
"version": "0.0.1", | ||
"version": "0.1.1", | ||
"description": "This module has code to integrate Blockaid PPOM with MetaMask", | ||
@@ -19,3 +19,3 @@ "homepage": "https://github.com/MetaMask/ppom-validator#readme", | ||
"scripts": { | ||
"build": "tsc --project tsconfig.build.json && cp node_modules/@blockaid/ppom-mock/dist/ppom_bg.wasm dist/ppom_bg.wasm", | ||
"build": "tsc --project tsconfig.build.json", | ||
"build:clean": "rimraf dist && yarn build", | ||
@@ -35,3 +35,2 @@ "build:docs": "typedoc", | ||
"dependencies": { | ||
"@blockaid/ppom-mock": "^1.0.0", | ||
"@metamask/base-controller": "^3.0.0", | ||
@@ -46,3 +45,3 @@ "@metamask/controller-utils": "^4.0.0", | ||
"@metamask/eslint-config": "^11.0.1", | ||
"@metamask/eslint-config-jest": "^11.0.0", | ||
"@metamask/eslint-config-jest": "^12.0.0", | ||
"@metamask/eslint-config-nodejs": "^11.0.1", | ||
@@ -49,0 +48,0 @@ "@metamask/eslint-config-typescript": "^11.0.0", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
3
905
1
100825
12
- Removed@blockaid/ppom-mock@^1.0.0
- Removed@blockaid/ppom-mock@1.0.1(transitive)