From 3cc578eb59b928663b78ea87859aeac92fcaaa15 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Fri, 3 Feb 2023 12:50:21 -0300 Subject: [PATCH] Integrate PS --- app/components/Nav/Main/RootRPCMethodsUI.js | 43 ++++ app/components/UI/DrawerView/index.js | 3 - .../InstallSnapApproval.tsx | 65 ++++++ .../UI/InstallSnapApproval/index.ts | 6 + .../UI/InstallSnapApproval/styles.ts | 52 +++++ .../UI/InstallSnapApproval/types.ts | 9 + app/core/Engine.js | 179 +++++++++++------ app/core/Permissions/constants.js | 9 + app/core/Permissions/specifications.js | 12 ++ app/core/RPCMethods/RPCMethodMiddleware.ts | 2 + app/core/Snaps/index.ts | 10 + app/core/Snaps/permissions.ts | 41 ++++ package.json | 3 + .../@metamask+snap-controllers+0.23.0.patch | 188 ++++-------------- yarn.lock | 86 +++++++- 15 files changed, 497 insertions(+), 211 deletions(-) create mode 100644 app/components/UI/InstallSnapApproval/InstallSnapApproval.tsx create mode 100644 app/components/UI/InstallSnapApproval/index.ts create mode 100644 app/components/UI/InstallSnapApproval/styles.ts create mode 100644 app/components/UI/InstallSnapApproval/types.ts create mode 100644 app/core/Snaps/permissions.ts diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 96b0151a194..7f9cf4d0bd1 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -35,6 +35,7 @@ import Logger from '../../../util/Logger'; import MessageSign from '../../UI/MessageSign'; import Approve from '../../Views/ApproveView/Approve'; import WatchAssetRequest from '../../UI/WatchAssetRequest'; +import { InstallSnapApproval } from '../../UI/InstallSnapApproval'; import AccountApproval from '../../UI/AccountApproval'; import TransactionTypes from '../../../core/TransactionTypes'; import AddCustomNetwork from '../../UI/AddCustomNetwork'; @@ -684,6 +685,47 @@ const RootRPCMethodsUI = (props) => { ); + const onInstallSnapConfirm = () => { + acceptPendingApproval(hostToApprove.id, hostToApprove.requestData); + setShowPendingApproval(false); + }; + + const onInstallSnapReject = () => { + // eslint-disable-next-line no-console + console.log( + 'onInstallSnapReject', + hostToApprove.id, + hostToApprove.requestData, + ); + rejectPendingApproval(hostToApprove.id, hostToApprove.requestData); + setShowPendingApproval(false); + }; + + /** + * Render the modal that asks the user to approve/reject connections to a dapp using the MetaMask SDK. + */ + const renderInstallSnapApprovalModal = () => ( + + + + ); + // unapprovedTransaction effect useEffect(() => { Engine.context.TransactionController.hub.on( @@ -815,6 +857,7 @@ const RootRPCMethodsUI = (props) => { {renderWatchAssetModal()} {renderQRSigningModal()} {renderAccountsApprovalModal()} + {renderInstallSnapApprovalModal()} ); }; diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index fd6f00fde69..57a619c1650 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -736,9 +736,6 @@ class DrawerView extends PureComponent { const { navigation } = this.props; navigation.navigate(Routes.SNAPS.HOME); this.hideDrawer(); - - // eslint-disable-next-line no-console - // console.log('Navigate to snaps'); }; showSettings = async () => { diff --git a/app/components/UI/InstallSnapApproval/InstallSnapApproval.tsx b/app/components/UI/InstallSnapApproval/InstallSnapApproval.tsx new file mode 100644 index 00000000000..9dda44ae7ac --- /dev/null +++ b/app/components/UI/InstallSnapApproval/InstallSnapApproval.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { InstallSnapApprovalArgs } from './types'; +import createStyles from './styles'; +import { useAppThemeFromContext, mockTheme } from '../../../util/theme'; +import StyledButton from '../StyledButton'; +import { strings } from '../../../../locales/i18n'; +import { + ACCOUNT_APROVAL_MODAL_CONTAINER_ID, + CANCEL_BUTTON_ID, +} from '../../../constants/test-ids'; + +const InstallSnapApproval = ({ + requestData, + onConfirm, + onCancel, +}: InstallSnapApprovalArgs) => { + const { colors } = useAppThemeFromContext() || mockTheme; + const styles = createStyles(colors); + + const selectedAddress = useSelector( + (state: any) => + state.engine.backgroundState.PreferencesController.selectedAddress, + ); + + const confirm = (): void => { + // eslint-disable-next-line no-console + console.log('confirm', onConfirm); + onConfirm(); + // Add track event + }; + + const cancel = (): void => { + // Add track event + onCancel(); + }; + + return ( + + {/* SNAP ID: {`${requestData.data.snapId}`} */} + {`${selectedAddress} ${JSON.stringify(requestData)}`} + + + {strings('accountApproval.cancel')} + + + Approve + + + + ); +}; + +export default InstallSnapApproval; diff --git a/app/components/UI/InstallSnapApproval/index.ts b/app/components/UI/InstallSnapApproval/index.ts new file mode 100644 index 00000000000..043b61328a1 --- /dev/null +++ b/app/components/UI/InstallSnapApproval/index.ts @@ -0,0 +1,6 @@ +import InstallSnapApproval from './InstallSnapApproval'; +import { InstallSnapApprovalArgs } from './types'; + +export { InstallSnapApproval }; + +export type { InstallSnapApprovalArgs }; diff --git a/app/components/UI/InstallSnapApproval/styles.ts b/app/components/UI/InstallSnapApproval/styles.ts new file mode 100644 index 00000000000..85c12f6dedd --- /dev/null +++ b/app/components/UI/InstallSnapApproval/styles.ts @@ -0,0 +1,52 @@ +import { StyleSheet } from 'react-native'; +import { fontStyles } from '../../../styles/common'; +import Device from '../../../util/device'; + +const createStyles = (colors: any) => + StyleSheet.create({ + root: { + backgroundColor: colors.background.default, + paddingTop: 24, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + minHeight: 200, + paddingBottom: Device.isIphoneX() ? 20 : 0, + }, + accountCardWrapper: { + paddingHorizontal: 24, + }, + intro: { + ...fontStyles.bold, + textAlign: 'center', + color: colors.text.default, + fontSize: Device.isSmallDevice() ? 16 : 20, + marginBottom: 8, + marginTop: 16, + }, + warning: { + ...fontStyles.thin, + color: colors.text.default, + paddingHorizontal: 24, + marginBottom: 16, + fontSize: 14, + width: '100%', + textAlign: 'center', + }, + actionContainer: { + flex: 0, + flexDirection: 'row', + paddingVertical: 16, + paddingHorizontal: 24, + }, + button: { + flex: 1, + }, + cancel: { + marginRight: 8, + }, + confirm: { + marginLeft: 8, + }, + }); + +export default createStyles; diff --git a/app/components/UI/InstallSnapApproval/types.ts b/app/components/UI/InstallSnapApproval/types.ts new file mode 100644 index 00000000000..5c5428ef789 --- /dev/null +++ b/app/components/UI/InstallSnapApproval/types.ts @@ -0,0 +1,9 @@ +interface InstallSnapApprovalArgs { + requestData: any; + onConfirm: () => void; + onCancel: () => void; + chainId?: string; +} + +// eslint-disable-next-line import/prefer-default-export +export type { InstallSnapApprovalArgs }; diff --git a/app/core/Engine.js b/app/core/Engine.js index be42079e9af..cd820a4ddb8 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -54,7 +54,12 @@ import NotificationManager from './NotificationManager'; import Logger from '../util/Logger'; import { LAST_INCOMING_TX_BLOCK_INFO } from '../constants/storage'; import { EndowmentPermissions } from '../constants/permissions'; -import { SNAP_BLOCKLIST, checkSnapsBlockList } from '../util/snaps'; +import { + SNAP_BLOCKLIST, + checkSnapsBlockList, + buildSnapEndowmentSpecifications, + buildSnapRestrictedMethodSpecifications, +} from '../util/snaps'; import { isZero } from '../util/lodash'; import { MetaMetricsEvents } from '../core/Analytics'; import AnalyticsV2 from '../util/analyticsV2'; @@ -252,6 +257,103 @@ class Engine { 'https://gas-api.metaswap.codefi.network/networks//suggestedGasFees', }); + const additionalKeyrings = [QRHardwareKeyring]; + + const getIdentities = () => { + const identities = preferencesController.state.identities; + const newIdentities = {}; + Object.keys(identities).forEach((key) => { + newIdentities[key.toLowerCase()] = identities[key]; + }); + return newIdentities; + }; + + const keyringController = new KeyringController( + { + removeIdentity: preferencesController.removeIdentity.bind( + preferencesController, + ), + syncIdentities: preferencesController.syncIdentities.bind( + preferencesController, + ), + updateIdentities: preferencesController.updateIdentities.bind( + preferencesController, + ), + setSelectedAddress: preferencesController.setSelectedAddress.bind( + preferencesController, + ), + setAccountLabel: preferencesController.setAccountLabel.bind( + preferencesController, + ), + }, + { encryptor, keyringTypes: additionalKeyrings }, + initialState.KeyringController, + ); + + const approvalController = new ApprovalController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'ApprovalController', + }), + showApprovalRequest: () => null, + }); + + const getSnapPermissionSpecifications = () => ({ + ...buildSnapEndowmentSpecifications(), + ...buildSnapRestrictedMethodSpecifications({ + clearSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:clearSnapState', + ), + // getMnemonic: this.getPrimaryKeyringMnemonic.bind(this), + // getUnlockPromise: this.appStateController.getUnlockPromise.bind( + // this.appStateController, + // ), + getSnap: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:get', + ), + handleSnapRpcRequest: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:handleRequest', + ), + getSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:getSnapState', + ), + // showConfirmation: (origin, confirmationData) => + // this.approvalController.addAndShowApprovalRequest({ + // origin, + // type: MESSAGE_TYPE.SNAP_CONFIRM, + // requestData: confirmationData, + // }), + updateSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:updateSnapState', + ), + }), + }); + + const permissionController = new PermissionController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'PermissionController', + allowedActions: [ + `${approvalController.name}:addRequest`, + `${approvalController.name}:hasRequest`, + `${approvalController.name}:acceptRequest`, + `${approvalController.name}:rejectRequest`, + ], + }), + state: initialState.PermissionController, + caveatSpecifications: getCaveatSpecifications({ getIdentities }), + permissionSpecifications: { + ...getPermissionSpecifications({ + getAllAccounts: () => keyringController.getAccounts(), + }), + ...getSnapPermissionSpecifications(), + }, + unrestrictedMethods, + }); + this.setupSnapProvider = (snapId, connectionStream) => { console.log( '[ENGINE LOG] Engine+setupSnapProvider: Setup stream for Snap', @@ -302,6 +404,16 @@ class Engine { 'ExecutionService:outboundResponse', ], allowedActions: [ + `${approvalController.name}:addRequest`, + `${permissionController.name}:getEndowments`, + `${permissionController.name}:getPermissions`, + `${permissionController.name}:hasPermission`, + `${permissionController.name}:hasPermissions`, + `${permissionController.name}:requestPermissions`, + `${permissionController.name}:revokeAllPermissions`, + `${permissionController.name}:revokePermissions`, + `${permissionController.name}:revokePermissionForAllSubjects`, + `${permissionController.name}:grantPermissions`, 'ExecutionService:executeSnap', 'ExecutionService:getRpcRequestHandler', 'ExecutionService:terminateSnap', @@ -313,6 +425,7 @@ class Engine { const snapController = new SnapController({ environmentEndowmentPermissions: Object.values(EndowmentPermissions), featureFlags: { dappsCanUpdateSnaps: true }, + // TO DO getAppKey: async () => new Promise((resolve, reject) => { resolve('mockAppKey'); @@ -324,55 +437,16 @@ class Engine { fetchFunction: RNFetchBlob.config({ fileCache: true }).fetch.bind( RNFetchBlob, ), + // TO DO closeAllConnections: () => console.log( 'TO DO: Create method to close all connections (Closes all connections for the given origin, and removes the references)', ), }); - const approvalController = new ApprovalController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'ApprovalController', - }), - showApprovalRequest: () => null, - }); - const phishingController = new PhishingController(); phishingController.updatePhishingLists(); - const additionalKeyrings = [QRHardwareKeyring]; - - const getIdentities = () => { - const identities = preferencesController.state.identities; - const newIdentities = {}; - Object.keys(identities).forEach((key) => { - newIdentities[key.toLowerCase()] = identities[key]; - }); - return newIdentities; - }; - - const keyringController = new KeyringController( - { - removeIdentity: preferencesController.removeIdentity.bind( - preferencesController, - ), - syncIdentities: preferencesController.syncIdentities.bind( - preferencesController, - ), - updateIdentities: preferencesController.updateIdentities.bind( - preferencesController, - ), - setSelectedAddress: preferencesController.setSelectedAddress.bind( - preferencesController, - ), - setAccountLabel: preferencesController.setAccountLabel.bind( - preferencesController, - ), - }, - { encryptor, keyringTypes: additionalKeyrings }, - initialState.KeyringController, - ); - const controllers = [ keyringController, new AccountTrackerController({ @@ -503,28 +577,7 @@ class Engine { ), gasFeeController, approvalController, - new PermissionController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'PermissionController', - allowedActions: [ - `${approvalController.name}:addRequest`, - `${approvalController.name}:hasRequest`, - `${approvalController.name}:acceptRequest`, - `${approvalController.name}:rejectRequest`, - ], - }), - state: initialState.PermissionController, - caveatSpecifications: getCaveatSpecifications({ getIdentities }), - permissionSpecifications: { - ...getPermissionSpecifications({ - getAllAccounts: () => keyringController.getAccounts(), - }), - /* - ...this.getSnapPermissionSpecifications(), - */ - }, - unrestrictedMethods, - }), + permissionController, snapController, ]; diff --git a/app/core/Permissions/constants.js b/app/core/Permissions/constants.js index 546945a4f6b..9f51c621608 100644 --- a/app/core/Permissions/constants.js +++ b/app/core/Permissions/constants.js @@ -4,4 +4,13 @@ export const CaveatTypes = Object.freeze({ export const RestrictedMethods = Object.freeze({ eth_accounts: 'eth_accounts', + // Snap Specific Restricted Methods + snap_confirm: 'snap_confirm', + snap_notify: 'snap_notify', + snap_manageState: 'snap_manageState', + snap_getBip32PublicKey: 'snap_getBip32PublicKey', + snap_getBip32Entropy: 'snap_getBip32Entropy', + snap_getBip44Entropy: 'snap_getBip44Entropy', + snap_getEntropy: 'snap_getEntropy', + 'wallet_snap_*': 'wallet_snap_*', }); diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index c0255d85943..c8c3aad5c8f 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -1,3 +1,5 @@ +import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snap-controllers'; +import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/rpc-methods'; import { constructPermission, PermissionType, @@ -64,6 +66,8 @@ export const getCaveatSpecifications = ({ getIdentities }) => ({ validator: (caveat, _origin, _target) => validateCaveatAccounts(caveat.value, getIdentities), }, + ...snapsCaveatsSpecifications, + ...snapsEndowmentCaveatSpecifications, }); /** @@ -132,6 +136,14 @@ export const getPermissionSpecifications = ({ getAllAccounts }) => ({ } }, }, + [PermissionKeys.snap_confirm]: { + permissionType: PermissionType.RestrictedMethod, + targetKey: PermissionKeys.snap_confirm, + }, + [PermissionKeys.snap_getBip44Entropy]: { + permissionType: PermissionType.RestrictedMethod, + targetKey: PermissionKeys.snap_getBip44Entropy, + }, }); /** diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 4cae41ac601..61bf5d0f47d 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -29,6 +29,8 @@ export enum ApprovalTypes { ADD_ETHEREUM_CHAIN = 'ADD_ETHEREUM_CHAIN', SWITCH_ETHEREUM_CHAIN = 'SWITCH_ETHEREUM_CHAIN', REQUEST_PERMISSIONS = 'wallet_requestPermissions', + INSTALL_SNAP = 'wallet_installSnap', + UPDATE_SNAP = 'wallet_updateSnap', } interface RPCMethodsMiddleParameters { diff --git a/app/core/Snaps/index.ts b/app/core/Snaps/index.ts index 88871986ebb..b8967a8def3 100644 --- a/app/core/Snaps/index.ts +++ b/app/core/Snaps/index.ts @@ -4,6 +4,12 @@ import WebviewExecutionService from './WebviewExecutionService'; import WebviewPostMessageStream from './WebviewPostMessageStream'; import SnapWebviewPostMessageStream from './SnapWebviewPostMessageStream'; import snapsState from './SnapsState'; +import { + buildSnapEndowmentSpecifications, + buildSnapRestrictedMethodSpecifications, + ExcludedSnapPermissions, + ExcludedSnapEndowments, +} from './permissions'; export { snapsState, @@ -12,4 +18,8 @@ export { WebviewExecutionService, WebviewPostMessageStream, SnapWebviewPostMessageStream, + buildSnapEndowmentSpecifications, + buildSnapRestrictedMethodSpecifications, + ExcludedSnapPermissions, + ExcludedSnapEndowments, }; diff --git a/app/core/Snaps/permissions.ts b/app/core/Snaps/permissions.ts new file mode 100644 index 00000000000..dce710ac7ce --- /dev/null +++ b/app/core/Snaps/permissions.ts @@ -0,0 +1,41 @@ +import { endowmentPermissionBuilders } from '@metamask/snap-controllers'; +import { + restrictedMethodPermissionBuilders, + selectHooks, +} from '@metamask/rpc-methods'; + +export const ExcludedSnapPermissions = new Set(['snap_dialog']); +export const ExcludedSnapEndowments = new Set(['endowment:keyring']); + +/** + * @returns {Record>} All endowment permission + * specifications. + */ +export const buildSnapEndowmentSpecifications = () => + Object.values(endowmentPermissionBuilders).reduce( + (allSpecifications, { targetKey, specificationBuilder }) => { + if (!ExcludedSnapEndowments.has(targetKey)) { + allSpecifications[targetKey] = specificationBuilder(); + } + return allSpecifications; + }, + {}, + ); + +/** + * @param {Record} hooks - The hooks for the Snap + * restricted method implementations. + */ +export function buildSnapRestrictedMethodSpecifications(hooks: any) { + return Object.values(restrictedMethodPermissionBuilders).reduce( + (specifications, { targetKey, specificationBuilder, methodHooks }) => { + if (!ExcludedSnapPermissions.has(targetKey)) { + specifications[targetKey] = specificationBuilder({ + methodHooks: selectHooks(hooks, methodHooks), + }); + } + return specifications; + }, + {}, + ); +} diff --git a/package.json b/package.json index 0b2eb0ce488..46cfcf42695 100644 --- a/package.json +++ b/package.json @@ -153,8 +153,11 @@ "@metamask/permission-controller": "^1.0.2", "@metamask/phishing-controller": "^1.1.0", "@metamask/preferences-controller": "^1.0.1", + "@metamask/rpc-methods": "0.24.1", + "@metamask/post-message-stream": "6.0.0", "@metamask/sdk-communication-layer": "^0.1.0", "@metamask/snap-controllers": "0.23.0", + "@metamask/snaps-utils": "0.24.1", "@metamask/swaps-controller": "^6.8.0", "@metamask/transaction-controller": "^1.0.0", "@ngraveio/bc-ur": "^1.1.6", diff --git a/patches/@metamask+snap-controllers+0.23.0.patch b/patches/@metamask+snap-controllers+0.23.0.patch index 20534694bba..7ed33b7d036 100644 --- a/patches/@metamask+snap-controllers+0.23.0.patch +++ b/patches/@metamask+snap-controllers+0.23.0.patch @@ -1,17 +1,16 @@ diff --git a/node_modules/@metamask/snap-controllers/dist/services/AbstractExecutionService.js b/node_modules/@metamask/snap-controllers/dist/services/AbstractExecutionService.js -index 90e8295..613f999 100644 +index 90e8295..7ac6243 100644 --- a/node_modules/@metamask/snap-controllers/dist/services/AbstractExecutionService.js +++ b/node_modules/@metamask/snap-controllers/dist/services/AbstractExecutionService.js -@@ -12,6 +12,8 @@ const nanoid_1 = require("nanoid"); +@@ -12,6 +12,7 @@ const nanoid_1 = require("nanoid"); const pump_1 = __importDefault(require("pump")); const json_rpc_middleware_stream_1 = require("json-rpc-middleware-stream"); const utils_2 = require("../utils"); +import { v4 as uuidv4 } from 'uuid'; -+ const controllerName = 'ExecutionService'; class AbstractExecutionService { constructor({ setupSnapProvider, messenger, terminationTimeout = utils_1.Duration.Second, }) { -@@ -43,6 +45,7 @@ class AbstractExecutionService { +@@ -43,6 +44,7 @@ class AbstractExecutionService { * @param jobId - The id of the job to be terminated. */ async terminate(jobId) { @@ -19,19 +18,18 @@ index 90e8295..613f999 100644 const jobWrapper = this.jobs.get(jobId); if (!jobWrapper) { throw new Error(`Job with id "${jobId}" not found.`); -@@ -52,8 +55,10 @@ class AbstractExecutionService { +@@ -52,8 +54,9 @@ class AbstractExecutionService { jsonrpc: '2.0', method: 'terminate', params: [], - id: (0, nanoid_1.nanoid)(), + id: uuidv4(), }), this._terminationTimeout); -+ + console.log('[EXEC SERVICE LOG] AbstractExecutionService+terminate: Snap result', result); if (result === utils_2.hasTimedOut || result !== 'OK') { // We tried to shutdown gracefully but failed. This probably means the Snap is in infite loop and // hogging down the whole JS process. -@@ -62,8 +67,10 @@ class AbstractExecutionService { +@@ -62,8 +65,10 @@ class AbstractExecutionService { // JS process. console.error(`Job "${jobId}" failed to terminate gracefully.`, result); } @@ -42,16 +40,15 @@ index 90e8295..613f999 100644 !stream.destroyed && stream.destroy(); stream.removeAllListeners(); } -@@ -74,7 +81,7 @@ class AbstractExecutionService { - this._terminate(jobWrapper); +@@ -75,6 +80,7 @@ class AbstractExecutionService { this._removeSnapAndJobMapping(jobId); this.jobs.delete(jobId); -- console.log(`Job "${jobId}" terminated.`); + console.log(`Job "${jobId}" terminated.`); + console.log('[EXEC SERVICE LOG] AbstractExecutionService+executeSnap: Job', jobId, 'terminated and deleted'); } /** * Initiates a job for a snap. -@@ -84,7 +91,7 @@ class AbstractExecutionService { +@@ -84,7 +90,7 @@ class AbstractExecutionService { * @returns Information regarding the created job. */ async _initJob() { @@ -60,7 +57,7 @@ index 90e8295..613f999 100644 const { streams, worker } = await this._initStreams(jobId); const rpcEngine = new json_rpc_engine_1.JsonRpcEngine(); const jsonRpcConnection = (0, json_rpc_middleware_stream_1.createStreamMiddleware)(); -@@ -97,6 +104,7 @@ class AbstractExecutionService { +@@ -97,6 +103,7 @@ class AbstractExecutionService { worker, }; this.jobs.set(jobId, envMetadata); @@ -68,7 +65,7 @@ index 90e8295..613f999 100644 return envMetadata; } /** -@@ -108,6 +116,7 @@ class AbstractExecutionService { +@@ -108,6 +115,7 @@ class AbstractExecutionService { * @returns The streams to communicate with the worker and the worker itself. */ async _initStreams(jobId) { @@ -76,15 +73,7 @@ index 90e8295..613f999 100644 const { worker, stream: envStream } = await this._initEnvStream(jobId); // Typecast justification: stream type mismatch const mux = setupMultiplex(envStream, `Job: "${jobId}"`); -@@ -159,6 +168,7 @@ class AbstractExecutionService { - * @param snapId - The ID of the snap to terminate. - */ - async terminateSnap(snapId) { -+ - const jobId = this.snapToJobMap.get(snapId); - if (jobId) { - await this.terminate(jobId); -@@ -187,16 +197,18 @@ class AbstractExecutionService { +@@ -187,6 +195,7 @@ class AbstractExecutionService { * @throws If the execution service returns an error. */ async executeSnap(snapData) { @@ -92,10 +81,7 @@ index 90e8295..613f999 100644 if (this.snapToJobMap.has(snapData.snapId)) { throw new Error(`Snap "${snapData.snapId}" is already being executed.`); } - const job = await this._initJob(); - this._mapSnapAndJob(snapData.snapId, job.id); - // Ping the worker to ensure that it started up -+ +@@ -196,7 +205,7 @@ class AbstractExecutionService { await this._command(job.id, { jsonrpc: '2.0', method: 'ping', @@ -104,7 +90,7 @@ index 90e8295..613f999 100644 }); const rpcStream = job.streams.rpc; this.setupSnapProvider(snapData.snapId, rpcStream); -@@ -204,7 +216,7 @@ class AbstractExecutionService { +@@ -204,7 +213,7 @@ class AbstractExecutionService { jsonrpc: '2.0', method: 'executeSnap', params: snapData, @@ -113,15 +99,15 @@ index 90e8295..613f999 100644 }); this._createSnapHooks(snapData.snapId, job.id); return result; -@@ -222,6 +234,7 @@ class AbstractExecutionService { +@@ -222,6 +231,7 @@ class AbstractExecutionService { if (response.error) { throw new Error(response.error.message); } -+ // console.log({ response }) ++ console.log('[EXEC SERVICE LOG] AbstractExecutionService+_command: response for jobId', jobId, '=>', response); return response.result; } _removeSnapHooks(snapId) { -@@ -230,7 +243,7 @@ class AbstractExecutionService { +@@ -230,7 +240,7 @@ class AbstractExecutionService { _createSnapHooks(snapId, workerId) { const rpcHook = async ({ origin, handler, request }) => { return await this._command(workerId, { @@ -130,7 +116,7 @@ index 90e8295..613f999 100644 jsonrpc: '2.0', method: 'snapRpc', params: { -@@ -241,6 +254,7 @@ class AbstractExecutionService { +@@ -241,6 +251,7 @@ class AbstractExecutionService { }, }); }; @@ -138,7 +124,7 @@ index 90e8295..613f999 100644 this._snapRpcHooks.set(snapId, rpcHook); } /** -@@ -270,7 +284,9 @@ class AbstractExecutionService { +@@ -270,7 +281,9 @@ class AbstractExecutionService { if (!snapId) { throw new Error(`job: "${jobId}" has no mapped snap.`); } @@ -149,10 +135,23 @@ index 90e8295..613f999 100644 this._removeSnapHooks(snapId); } diff --git a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js -index eb7e9bf..95b48aa 100644 +index eb7e9bf..38141a3 100644 --- a/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js +++ b/node_modules/@metamask/snap-controllers/dist/snaps/SnapController.js -@@ -378,6 +378,7 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -320,7 +320,12 @@ class SnapController extends controllers_1.BaseControllerV2 { + } + } + async _stopSnapsLastRequestPastMax() { ++ + const entries = [...this._snapsRuntimeData.entries()]; ++ entries.forEach((entry) => { ++ console.log('entry', { id: entry[0], activeReferences: entry[1].activeReferences, lastRequest: entry[1].lastRequest, pendingOutboundRequests: entry[1].pendingOutboundRequests, state: entry[1].state }); ++ // console.log({ snapId: entry.snapId, references: entry.activeReferences, pendingInboundRequests: runtime.pendingInboundRequests.length, lastRequest: timeSince(runtime.lastRequest), maxIdleTime: this._maxIdleTime }); ++ }) + return Promise.all(entries + .filter(([_snapId, runtime]) => runtime.activeReferences === 0 && + runtime.pendingInboundRequests.length === 0 && +@@ -378,6 +383,7 @@ class SnapController extends controllers_1.BaseControllerV2 { * @param snapId - The id of the Snap to start. */ async startSnap(snapId) { @@ -160,7 +159,7 @@ index eb7e9bf..95b48aa 100644 const runtime = this.getRuntimeExpect(snapId); if (this.state.snaps[snapId].enabled === false) { throw new Error(`Snap "${snapId}" is disabled.`); -@@ -456,6 +457,7 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -456,6 +462,7 @@ class SnapController extends controllers_1.BaseControllerV2 { * @param snapId - The snap to terminate. */ async terminateSnap(snapId) { @@ -168,24 +167,7 @@ index eb7e9bf..95b48aa 100644 await this.messagingSystem.call('ExecutionService:terminateSnap', snapId); this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId)); } -@@ -728,6 +730,7 @@ class SnapController extends controllers_1.BaseControllerV2 { - * snap couldn't be installed. - */ - async installSnaps(origin, requestedSnaps) { -+ console.log('[SNAP CONTROLLER LOG] SnapControllers+installSnaps: Install Snaps', requestedSnaps); - const result = {}; - await Promise.all(Object.entries(requestedSnaps).map(async ([snapId, { version: rawVersion }]) => { - const version = (0, snap_utils_1.resolveVersion)(rawVersion); -@@ -738,7 +741,7 @@ class SnapController extends controllers_1.BaseControllerV2 { - }; - return; - } -- if (await this.messagingSystem.call('PermissionController:hasPermission', origin, permissionName)) { -+ if (true) { - // Attempt to install and run the snap, storing any errors that - // occur during the process. - result[snapId] = Object.assign({}, (await this.processRequestedSnap(origin, snapId, version))); -@@ -750,6 +753,7 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -750,6 +757,7 @@ class SnapController extends controllers_1.BaseControllerV2 { }; } })); @@ -193,27 +175,22 @@ index eb7e9bf..95b48aa 100644 return result; } /** -@@ -806,12 +810,18 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -806,11 +814,14 @@ class SnapController extends controllers_1.BaseControllerV2 { id: snapId, versionRange, }); -- await this.authorize(origin, snapId); -+ + console.log('[SNAP CONTROLLER LOG] SnapControllers+processRequestedSnap: Snap', snapId, 'added'); -+ -+ // await this.authorize(origin, snapId); -+ + await this.authorize(origin, snapId); ++ console.log('[SNAP CONTROLLER LOG] SnapControllers+processRequestedSnap: Snap', snapId, 'authorized'); await this._startSnap({ snapId, sourceCode, }); -+ ++ console.log('[SNAP CONTROLLER LOG] SnapControllers+processRequestedSnap: Snap', snapId, 'started'); const truncated = this.getTruncatedExpect(snapId); -+ this.messagingSystem.publish(`SnapController:snapInstalled`, truncated); return truncated; - } -@@ -846,6 +856,7 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -846,6 +857,7 @@ class SnapController extends controllers_1.BaseControllerV2 { throw new Error(`Received invalid snap version range: "${newVersionRange}".`); } const newSnap = await this._fetchSnap(snapId, newVersionRange); @@ -221,48 +198,15 @@ index eb7e9bf..95b48aa 100644 const newVersion = newSnap.manifest.version; if (!(0, snap_utils_1.gtVersion)(newVersion, snap.version)) { console.warn(`Tried updating snap "${snapId}" within "${newVersionRange}" version range, but newer version "${snap.version}" is already installed`); -@@ -949,13 +960,19 @@ class SnapController extends controllers_1.BaseControllerV2 { - throw error; +@@ -950,6 +962,7 @@ class SnapController extends controllers_1.BaseControllerV2 { } } -+ async _startSnap(snapData) { + console.log('[SNAP CONTROLLER LOG] SnapControllers+_startSnap: Start snap', snapData.snapId); -+ const { snapId } = snapData; if (this.isRunning(snapId)) { throw new Error(`Snap "${snapId}" is already started.`); - } -+ - try { -- const result = await this._executeWithTimeout(snapId, this.messagingSystem.call('ExecutionService:executeSnap', Object.assign(Object.assign({}, snapData), { endowments: await this._getEndowments(snapId) }))); -+ const endowments = await this._getEndowments(snapId) -+ console.log('[SNAP CONTROLLER LOG] _startSnap - Current endowments =>', endowments); -+ const result = await this._executeWithTimeout(snapId, this.messagingSystem.call('ExecutionService:executeSnap', Object.assign(Object.assign({}, snapData), { endowments }))); - this.transition(snapId, snap_utils_1.SnapStatusEvents.Start); - return result; - } -@@ -978,8 +995,9 @@ class SnapController extends controllers_1.BaseControllerV2 { - async _getEndowments(snapId) { - let allEndowments = []; - for (const permissionName of this._environmentEndowmentPermissions) { -- if (await this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName)) { -- const endowments = await this.messagingSystem.call('PermissionController:getEndowments', snapId, permissionName); -+ if (true) { -+ const dummyPromise = () => Promise.resolve([]); -+ const endowments = await dummyPromise(); - if (endowments) { - // We don't have any guarantees about the type of the endowments - // value, so we have to guard at runtime. -@@ -991,6 +1009,7 @@ class SnapController extends controllers_1.BaseControllerV2 { - } - } - } -+ - const dedupedEndowments = [ - ...new Set([...snap_utils_1.DEFAULT_ENDOWMENTS, ...allEndowments]), - ]; -@@ -1103,21 +1122,23 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -1103,21 +1116,23 @@ class SnapController extends controllers_1.BaseControllerV2 { // Local snaps are mostly used for development purposes. Fetches were cached in the browser and were not requested // afterwards which lead to confusing development where old versions of snaps were installed. // Thus we disable caching @@ -273,7 +217,6 @@ index eb7e9bf..95b48aa 100644 throw new Error(`Invalid URL: Locally hosted Snaps must be hosted on localhost. Received URL: "${manifestUrl.toString()}"`); } - const manifest = await (await this._fetchFunction(manifestUrl.toString(), fetchOptions)).json(); -+ + const manifest = await (await this._fetchFunction('GET', manifestUrl.toString(), fetchOptions)).json(); (0, snap_utils_1.assertIsSnapManifest)(manifest); const { source: { location: { npm: { filePath, iconPath }, }, }, } = manifest; @@ -286,11 +229,12 @@ index eb7e9bf..95b48aa 100644 : undefined, ]); - (0, snap_utils_1.validateSnapShasum)(manifest, sourceCode); ++ // [TO DO] https://github.com/MetaMask/mobile-planning/issues/529 + // (0, snap_utils_1.validateSnapShasum)(manifest, sourceCode); return { manifest, sourceCode, svgIcon }; } /** -@@ -1228,6 +1249,7 @@ class SnapController extends controllers_1.BaseControllerV2 { +@@ -1228,6 +1243,7 @@ class SnapController extends controllers_1.BaseControllerV2 { // We need to set up this promise map to map snapIds to their respective startPromises, // because otherwise we would lose context on the correct startPromise. const startPromises = new Map(); @@ -298,47 +242,3 @@ index eb7e9bf..95b48aa 100644 const rpcHandler = async ({ origin, handler: handlerType, request, }) => { if (this.state.snaps[snapId].enabled === false) { throw new Error(`Snap "${snapId}" is disabled.`); -@@ -1294,9 +1316,9 @@ class SnapController extends controllers_1.BaseControllerV2 { - * @template PromiseValue - The value of the Promise. - */ - async _executeWithTimeout(snapId, promise, timer) { -- const isLongRunning = await this.messagingSystem.call('PermissionController:hasPermission', snapId, endowments_1.SnapEndowments.LongRunning); -+ // const isLongRunning = await this.messagingSystem.call('PermissionController:hasPermission', snapId, endowments_1.SnapEndowments.LongRunning); - // Long running snaps have timeouts disabled -- if (isLongRunning) { -+ if (true) { - return promise; - } - const result = await (0, utils_2.withTimeout)(promise, timer !== null && timer !== void 0 ? timer : this._maxRequestTime); -diff --git a/node_modules/@metamask/snap-controllers/dist/snaps/utils/npm.js b/node_modules/@metamask/snap-controllers/dist/snaps/utils/npm.js -index 6a49c06..70f5c8a 100644 ---- a/node_modules/@metamask/snap-controllers/dist/snaps/utils/npm.js -+++ b/node_modules/@metamask/snap-controllers/dist/snaps/utils/npm.js -@@ -53,7 +53,7 @@ exports.fetchNpmSnap = fetchNpmSnap; - */ - async function fetchNpmTarball(packageName, versionRange, registryUrl = exports.DEFAULT_NPM_REGISTRY, fetchFunction = fetch) { - var _a, _b, _c, _d; -- const packageMetadata = await (await fetchFunction(new URL(packageName, registryUrl).toString())).json(); -+ const packageMetadata = await (await fetchFunction('GET', new URL(packageName, registryUrl).toString())).json(); - if (!(0, utils_1.isObject)(packageMetadata)) { - throw new Error(`Failed to fetch package "${packageName}" metadata from npm.`); - } -@@ -71,11 +71,12 @@ async function fetchNpmTarball(packageName, versionRange, registryUrl = exports. - newTarballUrl.hostname = newRegistryUrl.hostname; - newTarballUrl.protocol = newRegistryUrl.protocol; - // Perform a raw fetch because we want the Response object itself. -- const tarballResponse = await fetchFunction(newTarballUrl.toString()); -- if (!tarballResponse.ok) { -- throw new Error(`Failed to fetch tarball for package "${packageName}".`); -- } -- const stream = await tarballResponse.blob().then((blob) => blob.stream()); -+ const tarballResponse = await fetchFunction('GET', newTarballUrl.toString()); -+ // if (!tarballResponse.ok) { -+ // throw new Error(`Failed to fetch tarball for package "${packageName}".`); -+ // } -+ // const stream = await tarballResponse.blob().then((blob) => blob.stream()); -+ console.log(await tarballResponse.blob()); - return [stream, targetVersion]; - } - //# sourceMappingURL=npm.js.map -\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3f75af248ad..ca64316df71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4399,6 +4399,14 @@ isomorphic-fetch "^3.0.0" punycode "^2.1.1" +"@metamask/post-message-stream@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-6.0.0.tgz#e8c26b1ef41452b2f1862004651ae3970b944868" + integrity sha512-z0l6UMW3z6W4qIhKSh48/6n2Q9AG1oOh9tus+Ncs9w6dAbkElKke+mXZ7tT6oAyE3T4JZLnDfGKbPWYoq+nrsA== + dependencies: + "@metamask/utils" "^2.0.0" + readable-stream "2.3.3" + "@metamask/post-message-stream@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-6.1.0.tgz#d36837ea02777e65c1151a8687d427f84bf7004f" @@ -4415,6 +4423,24 @@ "@metamask/base-controller" "^1.1.1" "@metamask/controller-utils" "^1.0.0" +"@metamask/providers@^10.2.0": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-10.2.1.tgz#61304940adeccc7421dcda30ffd1d834273cc77b" + integrity sha512-p2TXw2a1Nb8czntDGfeIYQnk4LLVbd5vlcb3GY//lylYlKdSqp+uUTegCvxiFblRDOT68jsY8Ib1VEEzVUOolA== + dependencies: + "@metamask/object-multiplex" "^1.1.0" + "@metamask/safe-event-emitter" "^2.0.0" + "@types/chrome" "^0.0.136" + detect-browser "^5.2.0" + eth-rpc-errors "^4.0.2" + extension-port-stream "^2.0.1" + fast-deep-equal "^2.0.1" + is-stream "^2.0.0" + json-rpc-engine "^6.1.0" + json-rpc-middleware-stream "^4.2.1" + pump "^3.0.0" + webextension-polyfill-ts "^0.25.0" + "@metamask/providers@^9.0.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-9.1.0.tgz#ccbbfd698eeb777c5c45aee91c3ad97e20eab20b" @@ -4433,6 +4459,21 @@ pump "^3.0.0" webextension-polyfill-ts "^0.25.0" +"@metamask/rpc-methods@0.24.1": + version "0.24.1" + resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.24.1.tgz#77bb9d3c0960a53b04aeec5e97967a7581a341f7" + integrity sha512-mUwN5Ya1F51p/yq81MAqUwR1D3R8CU1cLw3sKypfH0gVtwHNxXogp6Jeyv1VI/46Hrh4i9yXDztsRMOr+AjZEw== + dependencies: + "@metamask/controllers" "^32.0.2" + "@metamask/key-tree" "^6.0.0" + "@metamask/snaps-utils" "^0.24.1" + "@metamask/types" "^1.1.0" + "@metamask/utils" "^3.3.1" + "@noble/hashes" "^1.1.3" + eth-rpc-errors "^4.0.2" + nanoid "^3.1.31" + superstruct "^0.16.7" + "@metamask/rpc-methods@^0.23.0": version "0.23.0" resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.23.0.tgz#51e1ebb91891d7227d346b229a6f112b67984c8e" @@ -4525,6 +4566,34 @@ ses "^0.17.0" superstruct "^0.16.7" +"@metamask/snaps-types@^0.24.1": + version "0.24.1" + resolved "https://registry.yarnpkg.com/@metamask/snaps-types/-/snaps-types-0.24.1.tgz#f315321f954611a7bdb514cf786a9c008897a3b6" + integrity sha512-Kt6pacC+nEISJm2BSa9Gu9HvYApIk03THCl6pmG8/HQr5g6r+VXRtQ56nQw7pHAiyV6eKlSITl/rFY+dYuJg6Q== + dependencies: + "@metamask/providers" "^10.2.0" + "@metamask/snaps-utils" "^0.24.1" + "@metamask/types" "^1.1.0" + +"@metamask/snaps-utils@0.24.1", "@metamask/snaps-utils@^0.24.1": + version "0.24.1" + resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-0.24.1.tgz#b95678828a739a5cf0c4e25c0843094931d3a0c7" + integrity sha512-W0wtDcvZd/y6MGyPWKnOBQ4ORpUTSCy8cwuxS0NsMuV1FhLgl9RlTmNWUlUS+eEGkUiQxdX8l+W34Cfe4ZZnuA== + dependencies: + "@babel/core" "^7.18.6" + "@babel/types" "^7.18.7" + "@metamask/snaps-types" "^0.24.1" + "@metamask/utils" "^3.3.1" + "@noble/hashes" "^1.1.3" + "@scure/base" "^1.1.1" + cron-parser "^4.5.0" + eth-rpc-errors "^4.0.3" + fast-deep-equal "^3.1.3" + rfdc "^1.3.0" + semver "^7.3.7" + ses "^0.17.0" + superstruct "^0.16.7" + "@metamask/swaps-controller@^6.8.0": version "6.8.0" resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-6.8.0.tgz#c2c43173dc1101fab9ec918e7f7853f14072740b" @@ -4562,7 +4631,14 @@ resolved "https://registry.yarnpkg.com/@metamask/types/-/types-1.1.0.tgz#9bd14b33427932833c50c9187298804a18c2e025" integrity sha512-EEV/GjlYkOSfSPnYXfOosxa3TqYtIW3fhg6jdw+cok/OhMgNn4wCfbENFqjytrHMU2f7ZKtBAvtiP5V8H44sSw== -"@metamask/utils@^3.0.1": +"@metamask/utils@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-2.1.0.tgz#a65eaa0932b863383844ec323e05e293d8e718ab" + integrity sha512-4PHdo5B1ifpw6GbsdlDpp8oqA++rddSmt2pWBHtIGGL2tQMvmfHdaDDSns4JP9iC+AbMogVcUpv5Vt8ow1zsRA== + dependencies: + fast-deep-equal "^3.1.3" + +"@metamask/utils@^3.0.1", "@metamask/utils@^3.3.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.4.1.tgz#7df750733960ee2bde27a00eb58fcfdf80570be2" integrity sha512-FjhJrplzFiPNlNNuYXkY1ave55ULLZ3+kY/d3zaW5SjS5AjszlN7gsWz74VI32qME6VyD/gdoTOgqVbKlcEnag== @@ -16469,6 +16545,14 @@ json-rpc-middleware-stream@3.0.0, json-rpc-middleware-stream@^3.0.0: "@metamask/safe-event-emitter" "^2.0.0" readable-stream "^2.3.3" +json-rpc-middleware-stream@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-4.2.1.tgz#e5cb8795ebfd7503c6ceaa43daaf065687cc2f22" + integrity sha512-6QKayke/8lg0nTjOpRCq4JCgRx7bVybldmloIfY21HSDV0GUevcV9i8DJNvuKTJx4KR9EDIf6HTStAnEovGUvA== + dependencies: + "@metamask/safe-event-emitter" "^2.0.0" + readable-stream "^2.3.3" + json-rpc-random-id@^1.0.0, json-rpc-random-id@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz#ba49d96aded1444dbb8da3d203748acbbcdec8c8"