From e0c7edba706c9f853bafe16d9b5be1fb64e3a617 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Mon, 3 Jun 2024 16:04:12 -0700 Subject: [PATCH 01/37] WIP --- app/manifest/v2/chrome.json | 2 +- app/manifest/v3/chrome.json | 2 +- app/scripts/background.js | 135 ++++++++++++++++++++++++++++++++++-- app/scripts/inpage.js | 81 +++++++++++++++++++--- 4 files changed, 204 insertions(+), 16 deletions(-) diff --git a/app/manifest/v2/chrome.json b/app/manifest/v2/chrome.json index e3d68547824a..8dfcaa0c8c48 100644 --- a/app/manifest/v2/chrome.json +++ b/app/manifest/v2/chrome.json @@ -1,7 +1,7 @@ { "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'", "externally_connectable": { - "matches": ["https://metamask.io/*"], + "matches": ["file://*/*", "http://*/*", "https://*/*"], "ids": ["*"] }, "minimum_chrome_version": "89" diff --git a/app/manifest/v3/chrome.json b/app/manifest/v3/chrome.json index 79656e26f0f9..4d0d1a8f883f 100644 --- a/app/manifest/v3/chrome.json +++ b/app/manifest/v3/chrome.json @@ -3,7 +3,7 @@ "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';" }, "externally_connectable": { - "matches": ["https://metamask.io/*"], + "matches": ["file://*/*", "http://*/*", "https://*/*"], "ids": ["*"] }, "minimum_chrome_version": "89" diff --git a/app/scripts/background.js b/app/scripts/background.js index f08ce99e8f72..39b884eb5601 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -9,7 +9,7 @@ import './lib/setup-initial-state-hooks'; import EventEmitter from 'events'; -import { finished, pipeline } from 'readable-stream'; +import { Transform, finished, pipeline, Duplex } from 'readable-stream'; import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; @@ -195,7 +195,8 @@ const sendReadyMessageToTabs = async () => { // These are set after initialization let connectRemote; -let connectExternal; +let connectExternalLegacy; +let connectExternalDapp; browser.runtime.onConnect.addListener(async (...args) => { // Queue up connection attempts here, waiting until after initialization @@ -208,7 +209,16 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { // Queue up connection attempts here, waiting until after initialization await isInitialized; // This is set in `setupController`, which is called as part of initialization - connectExternal(...args); + const port = args[0]; + + if (port.sender.tab?.id) { + // unwrap envelope here + console.log('onConnectExternal inpage', ...args); + connectExternalDapp(...args); + } else { + console.log('onConnectExternal extension', ...args); + connectExternalLegacy(...args); + } }); function saveTimestamp() { @@ -766,12 +776,12 @@ export function setupController( } }); } - connectExternal(remotePort); + connectExternalLegacy(remotePort); } }; // communication with page or other extension - connectExternal = (remotePort) => { + connectExternalLegacy = (remotePort) => { ///: BEGIN:ONLY_INCLUDE_IF(desktop) if ( DesktopManager.isDesktopEnabled() && @@ -790,8 +800,121 @@ export function setupController( }); }; + connectExternalDapp = async (remotePort) => { + if (metamaskBlockedPorts.includes(remotePort.name)) { + return; + } + + // this is triggered when a new tab is opened, or origin(url) is changed + if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { + const tabId = remotePort.sender.tab.id; + const url = new URL(remotePort.sender.url); + const { origin } = url; + + // store the orgin to corresponding tab so it can provide infor for onActivated listener + if (!Object.keys(tabOriginMapping).includes(tabId)) { + tabOriginMapping[tabId] = origin; + } + // const connectSitePermissions = + // controller.permissionController.state.subjects[origin]; + // // when the dapp is not connected, connectSitePermissions is undefined + // const isConnectedToDapp = connectSitePermissions !== undefined; + // // when open a new tab, this event will trigger twice, only 2nd time is with dapp loaded + // const isTabLoaded = remotePort.sender.tab.title !== 'New Tab'; + + // // *** Emit DappViewed metric event when *** + // // - refresh the dapp + // // - open dapp in a new tab + // if (isConnectedToDapp && isTabLoaded) { + // emitDappViewedMetricEvent( + // origin, + // connectSitePermissions, + // controller.preferencesController, + // ); + // } + + remotePort.onMessage.addListener((msg) => { + if (msg.data && msg.data.method === MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS) { + requestAccountTabIds[origin] = tabId; + } + }); + } + + const portStream = + overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); + + class WalletStream extends Duplex { + constructor() { + super({objectMode: true}) + } + + _read(_size) { + // this.push() + } + _write(_value, _encoding, callback) { + console.log('wallet stream write', _value) + this.push(_value) + callback(); + } + } + + class TransformableInStream extends Transform { + constructor() { + super({objectMode: true}); + } + + // Filter and wrap caip-x envelope to metamask-provider multiplex stream + _transform(value, _encoding, callback) { + console.log('transformIn', value) + if (value.type === 'caip-x') { + this.push({ + name: 'metamask-provider', + data: value.data, + }); + } + callback(); + } + } + + class TransformableOutStream extends Transform { + constructor() { + super({objectMode: true}); + } + + // Filter and wrap metamask-provider multiplex stream to caip-x envelope + _transform(value, _encoding, callback) { + console.log('transformOut', value) + if (value.name === 'metamask-provider') { + this.push({ + type: 'caip-x', + data: value.data, + }); + } + callback(); + } + } + + const walletStream = new WalletStream(); + const transformInStream = new TransformableInStream(); + const transformOutStream = new TransformableOutStream(); + + pipeline( + portStream, + transformInStream, + walletStream, + transformOutStream, + portStream, + (err) => console.log('MetaMask wallet stream', err), + ); + + controller.setupUntrustedCommunication({ + connectionStream: walletStream, + sender: remotePort.sender, + }); + }; + if (overrides?.registerConnectListeners) { - overrides.registerConnectListeners(connectRemote, connectExternal); + overrides.registerConnectListeners(connectRemote, connectExternalLegacy); } // diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d00a0542db03..8fe2c39e1b0d 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -33,13 +33,13 @@ cleanContextForImports(); /* eslint-disable import/first */ import log from 'loglevel'; import { v4 as uuid } from 'uuid'; -import { WindowPostMessageStream } from '@metamask/post-message-stream'; +import PortStream from 'extension-port-stream'; +import { Transform, finished, pipeline, Duplex } from 'readable-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; // contexts -const CONTENT_SCRIPT = 'metamask-contentscript'; -const INPAGE = 'metamask-inpage'; +const EXTENSION_ID = 'nonfpcflonapegmnfeafnddgdniflbnk'; restoreContextAfterImports(); @@ -51,13 +51,78 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); if (shouldInjectProvider()) { // setup background connection - const metamaskStream = new WindowPostMessageStream({ - name: INPAGE, - target: CONTENT_SCRIPT, - }); + const extensionPort = chrome.runtime.connect(EXTENSION_ID); + const portStream = new PortStream(extensionPort); + + + class WalletStream extends Duplex { + constructor() { + super({objectMode: true}) + } + + _read(_size) { + // this.push() + } + _write(_value, _encoding, callback) { + console.log('wallet stream write', _value) + this.push(_value) + callback(); + } + } + + class TransformableInStream extends Transform { + constructor() { + super({objectMode: true}); + } + + // Filter and wrap caip-x envelope to metamask-provider multiplex stream + _transform(value, _encoding, callback) { + console.log('transformIn', value) + if (value.type === 'caip-x') { + this.push({ + name: 'metamask-provider', + data: value.data, + }); + } + callback(); + } + } + + class TransformableOutStream extends Transform { + constructor() { + super({objectMode: true}); + } + + // Filter and wrap metamask-provider multiplex stream to caip-x envelope + _transform(value, _encoding, callback) { + console.log('transformOut', value) + if (value.name === 'metamask-provider') { + this.push({ + type: 'caip-x', + data: value.data, + }); + } + callback(); + } + } + + const walletStream = new WalletStream(); + const transformInStream = new TransformableInStream(); + const transformOutStream = new TransformableOutStream(); + + pipeline( + portStream, + transformInStream, + walletStream, + transformOutStream, + portStream, + (err) => console.log('MetaMask inpage stream', err), + ); + + extensionPort.onMessage.addListener(console.log); initializeProvider({ - connectionStream: metamaskStream, + connectionStream: walletStream, logger: log, shouldShimWeb3: true, providerInfo: { From c1ae3dba80186729164ba0b0cc9e6185645d0598 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 4 Jun 2024 17:00:34 -0700 Subject: [PATCH 02/37] WIP --- app/scripts/inpage.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 8fe2c39e1b0d..83a499ef9723 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -60,15 +60,21 @@ if (shouldInjectProvider()) { super({objectMode: true}) } - _read(_size) { - // this.push() + _read(value) { + console.log('wallet stream read', value) } + _write(_value, _encoding, callback) { console.log('wallet stream write', _value) this.push(_value) callback(); } } + // { + // objectMode: true, + // read: () => undefined, + // write: processMessage, + // } class TransformableInStream extends Transform { constructor() { @@ -119,7 +125,7 @@ if (shouldInjectProvider()) { (err) => console.log('MetaMask inpage stream', err), ); - extensionPort.onMessage.addListener(console.log); + extensionPort.onMessage.addListener((message) => console.log('extensionPort onMessage', message)) initializeProvider({ connectionStream: walletStream, From 61e9697d10e0127fd778e7daf7c57dc3ced3b8c9 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 08:40:27 -0700 Subject: [PATCH 03/37] WIP PortStream bypass sanity check (working) --- app/scripts/background.js | 65 ++++++++++++++++++++++++++------------- app/scripts/inpage.js | 52 ++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 39b884eb5601..f2243c1bd307 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -9,7 +9,7 @@ import './lib/setup-initial-state-hooks'; import EventEmitter from 'events'; -import { Transform, finished, pipeline, Duplex } from 'readable-stream'; +import { Transform, finished, pipeline, Duplex, PassThrough } from 'readable-stream'; import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; @@ -843,20 +843,38 @@ export function setupController( const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - class WalletStream extends Duplex { - constructor() { - super({objectMode: true}) - } + class WalletStream extends Duplex { + constructor() { + super({objectMode: true}) + } - _read(_size) { - // this.push() - } - _write(_value, _encoding, callback) { - console.log('wallet stream write', _value) - this.push(_value) - callback(); + _read() { + return undefined; + } + + _write(value, _encoding, callback) { + console.log('wallet stream write', value) + if (value.name === 'metamask-provider') { + remotePort.postMessage({ + type: 'caip-x', + data: value.data, + }) + } + return callback(); + } } - } + + const walletStream = new WalletStream(); + remotePort.onMessage.addListener((message) => { + console.log('remotePort onMessage', message) + + if (message.type === 'caip-x') { + walletStream.push({ + name: 'metamask-provider', + data: message.data, + }); + } + }) class TransformableInStream extends Transform { constructor() { @@ -894,18 +912,21 @@ export function setupController( } } - const walletStream = new WalletStream(); + // const walletStream = new PassThrough({objectMode: true}); const transformInStream = new TransformableInStream(); const transformOutStream = new TransformableOutStream(); - pipeline( - portStream, - transformInStream, - walletStream, - transformOutStream, - portStream, - (err) => console.log('MetaMask wallet stream', err), - ); + // portStream.pipe(walletStream) + // walletStream.pipe(portStream) + + // pipeline( + // portStream, + // // transformInStream, + // walletStream, + // // transformOutStream, + // portStream, + // (err) => console.log('MetaMask wallet stream', err), + // ); controller.setupUntrustedCommunication({ connectionStream: walletStream, diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 83a499ef9723..74321ac49212 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -34,7 +34,7 @@ cleanContextForImports(); import log from 'loglevel'; import { v4 as uuid } from 'uuid'; import PortStream from 'extension-port-stream'; -import { Transform, finished, pipeline, Duplex } from 'readable-stream'; +import { Transform, finished, pipeline, Duplex, PassThrough } from 'readable-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; @@ -52,7 +52,7 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); if (shouldInjectProvider()) { // setup background connection const extensionPort = chrome.runtime.connect(EXTENSION_ID); - const portStream = new PortStream(extensionPort); + // const portStream = new PortStream(extensionPort); class WalletStream extends Duplex { @@ -60,16 +60,33 @@ if (shouldInjectProvider()) { super({objectMode: true}) } - _read(value) { - console.log('wallet stream read', value) + _read() { + return undefined; } - _write(_value, _encoding, callback) { - console.log('wallet stream write', _value) - this.push(_value) - callback(); + _write(value, _encoding, callback) { + console.log('wallet stream write', value) + if (value.name === 'metamask-provider') { + extensionPort.postMessage({ + type: 'caip-x', + data: value.data, + }) + } + return callback(); } } + + const walletStream = new WalletStream(); + extensionPort.onMessage.addListener((message) => { + console.log('extensionPort onMessage', message) + + if (message.type === 'caip-x') { + walletStream.push({ + name: 'metamask-provider', + data: message.data, + }); + } + }) // { // objectMode: true, // read: () => undefined, @@ -112,20 +129,19 @@ if (shouldInjectProvider()) { } } - const walletStream = new WalletStream(); + // const walletStream = new WalletStream(); const transformInStream = new TransformableInStream(); const transformOutStream = new TransformableOutStream(); - pipeline( - portStream, - transformInStream, - walletStream, - transformOutStream, - portStream, - (err) => console.log('MetaMask inpage stream', err), - ); + // pipeline( + // walletStream, + // // transformInStream, + // // transformOutStream, + // portStream, + // walletStream, + // (err) => console.log('MetaMask inpage stream', err), + // ); - extensionPort.onMessage.addListener((message) => console.log('extensionPort onMessage', message)) initializeProvider({ connectionStream: walletStream, From 1f8cfd2f1c481f995c4a3a5d203f50674aa54cfc Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 09:54:34 -0700 Subject: [PATCH 04/37] WIP wrapped stream (working) --- app/scripts/inpage.js | 75 +++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 74321ac49212..e42dd3d1f87d 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -52,12 +52,32 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); if (shouldInjectProvider()) { // setup background connection const extensionPort = chrome.runtime.connect(EXTENSION_ID); - // const portStream = new PortStream(extensionPort); + const portStream = new PortStream(extensionPort); + class Substream extends Duplex { + constructor({parentStream}) { + super({ + objectMode: true, + }); + this.parentStream = parentStream + } + + _read() { + return undefined; + } + + _write(value, _encoding, callback) { + console.log('substream write, push to parent', value) + this.parentStream.push(value) + callback() + } + } + class WalletStream extends Duplex { constructor() { super({objectMode: true}) + this.substream = new Substream({parentStream: this}) } _read() { @@ -65,33 +85,27 @@ if (shouldInjectProvider()) { } _write(value, _encoding, callback) { - console.log('wallet stream write', value) - if (value.name === 'metamask-provider') { - extensionPort.postMessage({ - type: 'caip-x', - data: value.data, - }) - } + console.log('wallet stream write, push to substream', value) + this.substream.push(value) return callback(); } } const walletStream = new WalletStream(); - extensionPort.onMessage.addListener((message) => { + // extensionPort.onMessage.addListener((message) => { + // console.log('extensionPort onMessage', message) + + // if (message.type === 'caip-x') { + // walletStream.push({ + // name: 'metamask-provider', + // data: message.data, + // }); + // } + // }) + + extensionPort.onMessage.addListener((message) => { console.log('extensionPort onMessage', message) - - if (message.type === 'caip-x') { - walletStream.push({ - name: 'metamask-provider', - data: message.data, - }); - } }) - // { - // objectMode: true, - // read: () => undefined, - // write: processMessage, - // } class TransformableInStream extends Transform { constructor() { @@ -133,18 +147,17 @@ if (shouldInjectProvider()) { const transformInStream = new TransformableInStream(); const transformOutStream = new TransformableOutStream(); - // pipeline( - // walletStream, - // // transformInStream, - // // transformOutStream, - // portStream, - // walletStream, - // (err) => console.log('MetaMask inpage stream', err), - // ); - + pipeline( + portStream, + transformInStream, + walletStream, + transformOutStream, + portStream, + (err) => console.log('MetaMask inpage stream front', err), + ); initializeProvider({ - connectionStream: walletStream, + connectionStream: walletStream.substream, logger: log, shouldShimWeb3: true, providerInfo: { From 5db9a9716ed90d431971dbd6605e5403ea07e76f Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 10:38:55 -0700 Subject: [PATCH 05/37] cleanup inpage --- app/scripts/inpage.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index e42dd3d1f87d..265c678d5a7b 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -54,6 +54,10 @@ if (shouldInjectProvider()) { const extensionPort = chrome.runtime.connect(EXTENSION_ID); const portStream = new PortStream(extensionPort); + extensionPort.onMessage.addListener((message) => { + console.log('extensionPort onMessage', message) + }) + class Substream extends Duplex { constructor({parentStream}) { @@ -90,23 +94,6 @@ if (shouldInjectProvider()) { return callback(); } } - - const walletStream = new WalletStream(); - // extensionPort.onMessage.addListener((message) => { - // console.log('extensionPort onMessage', message) - - // if (message.type === 'caip-x') { - // walletStream.push({ - // name: 'metamask-provider', - // data: message.data, - // }); - // } - // }) - - extensionPort.onMessage.addListener((message) => { - console.log('extensionPort onMessage', message) - }) - class TransformableInStream extends Transform { constructor() { super({objectMode: true}); @@ -143,7 +130,7 @@ if (shouldInjectProvider()) { } } - // const walletStream = new WalletStream(); + const walletStream = new WalletStream(); const transformInStream = new TransformableInStream(); const transformOutStream = new TransformableOutStream(); From 6ef86a430fec0a644ea91e20c5b867eaff45f305 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 14:54:25 -0700 Subject: [PATCH 06/37] DRY caip stream --- app/scripts/background.js | 90 ++------------------------ app/scripts/inpage.js | 94 +--------------------------- shared/modules/create-caip-stream.ts | 80 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 177 deletions(-) create mode 100644 shared/modules/create-caip-stream.ts diff --git a/app/scripts/background.js b/app/scripts/background.js index f2243c1bd307..7c4b8f39c9fc 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -9,7 +9,7 @@ import './lib/setup-initial-state-hooks'; import EventEmitter from 'events'; -import { Transform, finished, pipeline, Duplex, PassThrough } from 'readable-stream'; +import { finished, pipeline } from 'readable-stream'; import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; @@ -50,6 +50,7 @@ import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; import { SENTRY_BACKGROUND_STATE } from './lib/setupSentry'; +import { createCaipStream } from '../../shared/modules/create-caip-stream'; import createStreamSink from './lib/createStreamSink'; import NotificationManager, { NOTIFICATION_MANAGER_EVENTS, @@ -843,93 +844,10 @@ export function setupController( const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - class WalletStream extends Duplex { - constructor() { - super({objectMode: true}) - } - - _read() { - return undefined; - } - - _write(value, _encoding, callback) { - console.log('wallet stream write', value) - if (value.name === 'metamask-provider') { - remotePort.postMessage({ - type: 'caip-x', - data: value.data, - }) - } - return callback(); - } - } - - const walletStream = new WalletStream(); - remotePort.onMessage.addListener((message) => { - console.log('remotePort onMessage', message) - - if (message.type === 'caip-x') { - walletStream.push({ - name: 'metamask-provider', - data: message.data, - }); - } - }) - - class TransformableInStream extends Transform { - constructor() { - super({objectMode: true}); - } - - // Filter and wrap caip-x envelope to metamask-provider multiplex stream - _transform(value, _encoding, callback) { - console.log('transformIn', value) - if (value.type === 'caip-x') { - this.push({ - name: 'metamask-provider', - data: value.data, - }); - } - callback(); - } - } - - class TransformableOutStream extends Transform { - constructor() { - super({objectMode: true}); - } - - // Filter and wrap metamask-provider multiplex stream to caip-x envelope - _transform(value, _encoding, callback) { - console.log('transformOut', value) - if (value.name === 'metamask-provider') { - this.push({ - type: 'caip-x', - data: value.data, - }); - } - callback(); - } - } - - // const walletStream = new PassThrough({objectMode: true}); - const transformInStream = new TransformableInStream(); - const transformOutStream = new TransformableOutStream(); - - // portStream.pipe(walletStream) - // walletStream.pipe(portStream) - - // pipeline( - // portStream, - // // transformInStream, - // walletStream, - // // transformOutStream, - // portStream, - // (err) => console.log('MetaMask wallet stream', err), - // ); + const connectionStream = createCaipStream(portStream); controller.setupUntrustedCommunication({ - connectionStream: walletStream, + connectionStream, sender: remotePort.sender, }); }; diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 265c678d5a7b..62623ab3b0a4 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -34,9 +34,9 @@ cleanContextForImports(); import log from 'loglevel'; import { v4 as uuid } from 'uuid'; import PortStream from 'extension-port-stream'; -import { Transform, finished, pipeline, Duplex, PassThrough } from 'readable-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; +import { createCaipStream } from '../../shared/modules/create-caip-stream'; // contexts const EXTENSION_ID = 'nonfpcflonapegmnfeafnddgdniflbnk'; @@ -53,98 +53,10 @@ if (shouldInjectProvider()) { // setup background connection const extensionPort = chrome.runtime.connect(EXTENSION_ID); const portStream = new PortStream(extensionPort); - - extensionPort.onMessage.addListener((message) => { - console.log('extensionPort onMessage', message) - }) - - - class Substream extends Duplex { - constructor({parentStream}) { - super({ - objectMode: true, - }); - this.parentStream = parentStream - } - - _read() { - return undefined; - } - - _write(value, _encoding, callback) { - console.log('substream write, push to parent', value) - this.parentStream.push(value) - callback() - } - } - - class WalletStream extends Duplex { - constructor() { - super({objectMode: true}) - this.substream = new Substream({parentStream: this}) - } - - _read() { - return undefined; - } - - _write(value, _encoding, callback) { - console.log('wallet stream write, push to substream', value) - this.substream.push(value) - return callback(); - } - } - class TransformableInStream extends Transform { - constructor() { - super({objectMode: true}); - } - - // Filter and wrap caip-x envelope to metamask-provider multiplex stream - _transform(value, _encoding, callback) { - console.log('transformIn', value) - if (value.type === 'caip-x') { - this.push({ - name: 'metamask-provider', - data: value.data, - }); - } - callback(); - } - } - - class TransformableOutStream extends Transform { - constructor() { - super({objectMode: true}); - } - - // Filter and wrap metamask-provider multiplex stream to caip-x envelope - _transform(value, _encoding, callback) { - console.log('transformOut', value) - if (value.name === 'metamask-provider') { - this.push({ - type: 'caip-x', - data: value.data, - }); - } - callback(); - } - } - - const walletStream = new WalletStream(); - const transformInStream = new TransformableInStream(); - const transformOutStream = new TransformableOutStream(); - - pipeline( - portStream, - transformInStream, - walletStream, - transformOutStream, - portStream, - (err) => console.log('MetaMask inpage stream front', err), - ); + const connectionStream = createCaipStream(portStream); initializeProvider({ - connectionStream: walletStream.substream, + connectionStream, logger: log, shouldShimWeb3: true, providerInfo: { diff --git a/shared/modules/create-caip-stream.ts b/shared/modules/create-caip-stream.ts new file mode 100644 index 000000000000..7b9cdc25b9a8 --- /dev/null +++ b/shared/modules/create-caip-stream.ts @@ -0,0 +1,80 @@ +import { isObject } from '@metamask/utils'; +import PortStream from 'extension-port-stream'; +import { Transform, pipeline, Duplex } from 'readable-stream'; + +export class SplitStream extends Duplex { + substream: Duplex; + + constructor(substream?: SplitStream) { + super({ objectMode: true }); + this.substream = substream ?? new SplitStream(this); + } + + _read() {} + + _write( + value: unknown, + _encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + this.substream.push(value); + callback(); + } +} + +export class CaipToMultiplexStream extends Transform { + constructor() { + super({ objectMode: true }); + } + + _write( + value: unknown, + _encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + if (isObject(value) && value.type === 'caip-x') { + this.push({ + name: 'metamask-provider', + data: value.data, + }); + } + callback(); + } +} + +export class MultiplexToCaipStream extends Transform { + constructor() { + super({ objectMode: true }); + } + + _write( + value: unknown, + _encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + if (isObject(value) && value.name === 'metamask-provider') { + this.push({ + type: 'caip-x', + data: value.data, + }); + } + callback(); + } +} + +export const createCaipStream = (portStream: PortStream): Duplex => { + const splitStream = new SplitStream(); + const caipToMultiplexStream = new CaipToMultiplexStream(); + const multiplexToCaipStream = new MultiplexToCaipStream(); + + pipeline( + portStream, + caipToMultiplexStream, + splitStream, + multiplexToCaipStream, + portStream, + (err: Error) => console.log('MetaMask CAIP stream', err), + ); + + return splitStream.substream; +}; From 1f58e7efdd5cf35ba17d2445302d1169e6c1bce9 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 15:43:48 -0700 Subject: [PATCH 07/37] Rename. WIP spec --- app/scripts/background.js | 2 +- app/scripts/inpage.js | 2 +- shared/modules/caip-stream.test.ts | 38 +++++++++++++++++++ .../{create-caip-stream.ts => caip-stream.ts} | 0 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 shared/modules/caip-stream.test.ts rename shared/modules/{create-caip-stream.ts => caip-stream.ts} (100%) diff --git a/app/scripts/background.js b/app/scripts/background.js index e8a4b3eb7eba..4a7426e974af 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -50,7 +50,7 @@ import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; import { SENTRY_BACKGROUND_STATE } from './lib/setupSentry'; -import { createCaipStream } from '../../shared/modules/create-caip-stream'; +import { createCaipStream } from '../../shared/modules/caip-stream'; import createStreamSink from './lib/createStreamSink'; import NotificationManager, { NOTIFICATION_MANAGER_EVENTS, diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 62623ab3b0a4..4595581b7df3 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -36,7 +36,7 @@ import { v4 as uuid } from 'uuid'; import PortStream from 'extension-port-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; -import { createCaipStream } from '../../shared/modules/create-caip-stream'; +import { createCaipStream } from '../../shared/modules/caip-stream'; // contexts const EXTENSION_ID = 'nonfpcflonapegmnfeafnddgdniflbnk'; diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts new file mode 100644 index 000000000000..a04e51db159f --- /dev/null +++ b/shared/modules/caip-stream.test.ts @@ -0,0 +1,38 @@ +import { + createCaipStream, + SplitStream, + CaipToMultiplexStream, + MultiplexToCaipStream, +} from './caip-stream' +import { deferredPromise } from '../../app/scripts/lib/util' +import { PassThrough } from 'readable-stream' +import { WriteStream } from 'fs' + +describe('CAIP Stream', () => { + describe('SplitStream', () => { + it('redirects writes to its substream', async () => { + const splitStream = new SplitStream() + + const outerStreamChunks: unknown[] = [] + splitStream.on('data', (chunk: unknown) => { + outerStreamChunks.push(chunk) + }) + + const innerStreamChunks: unknown[] = [] + splitStream.substream.on('data', (chunk: unknown) => { + innerStreamChunks.push(chunk) + }) + + const { + promise: isWritten, + resolve: writeCallback, + } = deferredPromise(); + + splitStream.write({foo: 'bar'}, writeCallback) + + await isWritten + expect(outerStreamChunks).toStrictEqual([]) + expect(innerStreamChunks).toStrictEqual([{foo: 'bar'}]) + }) + }) +}) diff --git a/shared/modules/create-caip-stream.ts b/shared/modules/caip-stream.ts similarity index 100% rename from shared/modules/create-caip-stream.ts rename to shared/modules/caip-stream.ts From a9b741f177b0ff2f6d30596bb7b4d6ecd310f7e7 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 15:53:25 -0700 Subject: [PATCH 08/37] add SplitStream specs --- shared/modules/caip-stream.test.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index a04e51db159f..816c478e53a3 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -10,7 +10,7 @@ import { WriteStream } from 'fs' describe('CAIP Stream', () => { describe('SplitStream', () => { - it('redirects writes to its substream', async () => { + it('redirects writes from the main stream to the substream', async () => { const splitStream = new SplitStream() const outerStreamChunks: unknown[] = [] @@ -34,5 +34,30 @@ describe('CAIP Stream', () => { expect(outerStreamChunks).toStrictEqual([]) expect(innerStreamChunks).toStrictEqual([{foo: 'bar'}]) }) + + it('redirects writes from the substream to the main stream', async () => { + const splitStream = new SplitStream() + + const outerStreamChunks: unknown[] = [] + splitStream.on('data', (chunk: unknown) => { + outerStreamChunks.push(chunk) + }) + + const innerStreamChunks: unknown[] = [] + splitStream.substream.on('data', (chunk: unknown) => { + innerStreamChunks.push(chunk) + }) + + const { + promise: isWritten, + resolve: writeCallback, + } = deferredPromise(); + + splitStream.substream.write({foo: 'bar'}, writeCallback) + + await isWritten + expect(outerStreamChunks).toStrictEqual([{foo: 'bar'}]) + expect(innerStreamChunks).toStrictEqual([]) + }) }) }) From 7df64f3bbf41305d8b23c287e5fb81640ec7b7d6 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 16:02:33 -0700 Subject: [PATCH 09/37] add CaipToMultiplexStream, MultiplexToCaipStream specs --- shared/modules/caip-stream.test.ts | 95 +++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 816c478e53a3..01162c67d558 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -5,8 +5,19 @@ import { MultiplexToCaipStream, } from './caip-stream' import { deferredPromise } from '../../app/scripts/lib/util' -import { PassThrough } from 'readable-stream' -import { WriteStream } from 'fs' +import { Writable } from 'readable-stream'; +import { Duplex } from 'stream'; + +const writeToStream = async (stream: Duplex, message: unknown) => { + const { + promise: isWritten, + resolve: writeCallback, + } = deferredPromise(); + + stream.write(message, writeCallback) + await isWritten +} + describe('CAIP Stream', () => { describe('SplitStream', () => { @@ -23,14 +34,8 @@ describe('CAIP Stream', () => { innerStreamChunks.push(chunk) }) - const { - promise: isWritten, - resolve: writeCallback, - } = deferredPromise(); - - splitStream.write({foo: 'bar'}, writeCallback) + await writeToStream(splitStream, {foo: 'bar'}) - await isWritten expect(outerStreamChunks).toStrictEqual([]) expect(innerStreamChunks).toStrictEqual([{foo: 'bar'}]) }) @@ -48,16 +53,74 @@ describe('CAIP Stream', () => { innerStreamChunks.push(chunk) }) - const { - promise: isWritten, - resolve: writeCallback, - } = deferredPromise(); + await writeToStream(splitStream.substream, {foo: 'bar'}) - splitStream.substream.write({foo: 'bar'}, writeCallback) - - await isWritten expect(outerStreamChunks).toStrictEqual([{foo: 'bar'}]) expect(innerStreamChunks).toStrictEqual([]) }) }) + + describe('CaipToMultiplexStream', () => { + it('drops non caip-x messages', async () => { + const caipToMultiplexStream = new CaipToMultiplexStream() + + const streamChunks: unknown[] = [] + caipToMultiplexStream.on('data', (chunk: unknown) => { + streamChunks.push(chunk) + }) + + await writeToStream(caipToMultiplexStream, {foo: 'bar'}) + await writeToStream(caipToMultiplexStream, {type: 'caip-wrong', data: {foo: 'bar'}}) + + expect(streamChunks).toStrictEqual([]) + }) + + it('rewraps caip-x messages into multiplexed `metamask-provider` messages', async () => { + const caipToMultiplexStream = new CaipToMultiplexStream() + + const streamChunks: unknown[] = [] + caipToMultiplexStream.on('data', (chunk: unknown) => { + streamChunks.push(chunk) + }) + + await writeToStream(caipToMultiplexStream, {type: 'caip-x', data: {foo: 'bar'}}) + + expect(streamChunks).toStrictEqual([{ + name: 'metamask-provider', + data: {foo: 'bar'} + }]) + }) + }) + + describe('MultiplexToCaipStream', () => { + it('drops non multiplexed `metamask-provider` messages', async () => { + const multiplexToCaipStream = new MultiplexToCaipStream() + + const streamChunks: unknown[] = [] + multiplexToCaipStream.on('data', (chunk: unknown) => { + streamChunks.push(chunk) + }) + + await writeToStream(multiplexToCaipStream, {foo: 'bar'}) + await writeToStream(multiplexToCaipStream, {name: 'wrong-multiplex', data: {foo: 'bar'}}) + + expect(streamChunks).toStrictEqual([]) + }) + + it('rewraps multiplexed `metamask-provider` messages into caip-x messages', async () => { + const multiplexToCaipStream = new MultiplexToCaipStream() + + const streamChunks: unknown[] = [] + multiplexToCaipStream.on('data', (chunk: unknown) => { + streamChunks.push(chunk) + }) + + await writeToStream(multiplexToCaipStream, {name: 'metamask-provider', data: {foo: 'bar'}}) + + expect(streamChunks).toStrictEqual([{ + type: 'caip-x', + data: {foo: 'bar'} + }]) + }) + }) }) From 7db9d7505a75c80a8dfa93d28e9ea104486f9506 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 16:04:52 -0700 Subject: [PATCH 10/37] lint --- app/scripts/background.js | 2 +- shared/modules/caip-stream.test.ts | 163 +++++++++++++++-------------- 2 files changed, 88 insertions(+), 77 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 4a7426e974af..c5ca9cb341d5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -43,6 +43,7 @@ import { checkForLastErrorAndLog } from '../../shared/modules/browser-runtime.ut import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { maskObject } from '../../shared/modules/object.utils'; import { FIXTURE_STATE_METADATA_VERSION } from '../../test/e2e/default-fixture'; +import { createCaipStream } from '../../shared/modules/caip-stream'; import migrations from './migrations'; import Migrator from './lib/migrator'; import ExtensionPlatform from './platforms/extension'; @@ -50,7 +51,6 @@ import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; import { SENTRY_BACKGROUND_STATE } from './lib/setupSentry'; -import { createCaipStream } from '../../shared/modules/caip-stream'; import createStreamSink from './lib/createStreamSink'; import NotificationManager, { NOTIFICATION_MANAGER_EVENTS, diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 01162c67d558..e33102cd415d 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -1,126 +1,137 @@ +import { Duplex } from 'stream'; +import { deferredPromise } from '../../app/scripts/lib/util'; import { createCaipStream, SplitStream, CaipToMultiplexStream, MultiplexToCaipStream, -} from './caip-stream' -import { deferredPromise } from '../../app/scripts/lib/util' -import { Writable } from 'readable-stream'; -import { Duplex } from 'stream'; +} from './caip-stream'; const writeToStream = async (stream: Duplex, message: unknown) => { - const { - promise: isWritten, - resolve: writeCallback, - } = deferredPromise(); - - stream.write(message, writeCallback) - await isWritten -} + const { promise: isWritten, resolve: writeCallback } = deferredPromise(); + stream.write(message, writeCallback); + await isWritten; +}; describe('CAIP Stream', () => { describe('SplitStream', () => { it('redirects writes from the main stream to the substream', async () => { - const splitStream = new SplitStream() + const splitStream = new SplitStream(); - const outerStreamChunks: unknown[] = [] + const outerStreamChunks: unknown[] = []; splitStream.on('data', (chunk: unknown) => { - outerStreamChunks.push(chunk) - }) + outerStreamChunks.push(chunk); + }); - const innerStreamChunks: unknown[] = [] + const innerStreamChunks: unknown[] = []; splitStream.substream.on('data', (chunk: unknown) => { - innerStreamChunks.push(chunk) - }) + innerStreamChunks.push(chunk); + }); - await writeToStream(splitStream, {foo: 'bar'}) + await writeToStream(splitStream, { foo: 'bar' }); - expect(outerStreamChunks).toStrictEqual([]) - expect(innerStreamChunks).toStrictEqual([{foo: 'bar'}]) - }) + expect(outerStreamChunks).toStrictEqual([]); + expect(innerStreamChunks).toStrictEqual([{ foo: 'bar' }]); + }); it('redirects writes from the substream to the main stream', async () => { - const splitStream = new SplitStream() + const splitStream = new SplitStream(); - const outerStreamChunks: unknown[] = [] + const outerStreamChunks: unknown[] = []; splitStream.on('data', (chunk: unknown) => { - outerStreamChunks.push(chunk) - }) + outerStreamChunks.push(chunk); + }); - const innerStreamChunks: unknown[] = [] + const innerStreamChunks: unknown[] = []; splitStream.substream.on('data', (chunk: unknown) => { - innerStreamChunks.push(chunk) - }) + innerStreamChunks.push(chunk); + }); - await writeToStream(splitStream.substream, {foo: 'bar'}) + await writeToStream(splitStream.substream, { foo: 'bar' }); - expect(outerStreamChunks).toStrictEqual([{foo: 'bar'}]) - expect(innerStreamChunks).toStrictEqual([]) - }) - }) + expect(outerStreamChunks).toStrictEqual([{ foo: 'bar' }]); + expect(innerStreamChunks).toStrictEqual([]); + }); + }); describe('CaipToMultiplexStream', () => { it('drops non caip-x messages', async () => { - const caipToMultiplexStream = new CaipToMultiplexStream() + const caipToMultiplexStream = new CaipToMultiplexStream(); - const streamChunks: unknown[] = [] + const streamChunks: unknown[] = []; caipToMultiplexStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk) - }) + streamChunks.push(chunk); + }); - await writeToStream(caipToMultiplexStream, {foo: 'bar'}) - await writeToStream(caipToMultiplexStream, {type: 'caip-wrong', data: {foo: 'bar'}}) + await writeToStream(caipToMultiplexStream, { foo: 'bar' }); + await writeToStream(caipToMultiplexStream, { + type: 'caip-wrong', + data: { foo: 'bar' }, + }); - expect(streamChunks).toStrictEqual([]) - }) + expect(streamChunks).toStrictEqual([]); + }); it('rewraps caip-x messages into multiplexed `metamask-provider` messages', async () => { - const caipToMultiplexStream = new CaipToMultiplexStream() + const caipToMultiplexStream = new CaipToMultiplexStream(); - const streamChunks: unknown[] = [] + const streamChunks: unknown[] = []; caipToMultiplexStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk) - }) + streamChunks.push(chunk); + }); - await writeToStream(caipToMultiplexStream, {type: 'caip-x', data: {foo: 'bar'}}) - - expect(streamChunks).toStrictEqual([{ - name: 'metamask-provider', - data: {foo: 'bar'} - }]) - }) - }) + await writeToStream(caipToMultiplexStream, { + type: 'caip-x', + data: { foo: 'bar' }, + }); + + expect(streamChunks).toStrictEqual([ + { + name: 'metamask-provider', + data: { foo: 'bar' }, + }, + ]); + }); + }); describe('MultiplexToCaipStream', () => { it('drops non multiplexed `metamask-provider` messages', async () => { - const multiplexToCaipStream = new MultiplexToCaipStream() + const multiplexToCaipStream = new MultiplexToCaipStream(); - const streamChunks: unknown[] = [] + const streamChunks: unknown[] = []; multiplexToCaipStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk) - }) + streamChunks.push(chunk); + }); - await writeToStream(multiplexToCaipStream, {foo: 'bar'}) - await writeToStream(multiplexToCaipStream, {name: 'wrong-multiplex', data: {foo: 'bar'}}) + await writeToStream(multiplexToCaipStream, { foo: 'bar' }); + await writeToStream(multiplexToCaipStream, { + name: 'wrong-multiplex', + data: { foo: 'bar' }, + }); - expect(streamChunks).toStrictEqual([]) - }) + expect(streamChunks).toStrictEqual([]); + }); it('rewraps multiplexed `metamask-provider` messages into caip-x messages', async () => { - const multiplexToCaipStream = new MultiplexToCaipStream() + const multiplexToCaipStream = new MultiplexToCaipStream(); - const streamChunks: unknown[] = [] + const streamChunks: unknown[] = []; multiplexToCaipStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk) - }) - - await writeToStream(multiplexToCaipStream, {name: 'metamask-provider', data: {foo: 'bar'}}) + streamChunks.push(chunk); + }); - expect(streamChunks).toStrictEqual([{ - type: 'caip-x', - data: {foo: 'bar'} - }]) - }) - }) -}) + await writeToStream(multiplexToCaipStream, { + name: 'metamask-provider', + data: { foo: 'bar' }, + }); + + expect(streamChunks).toStrictEqual([ + { + type: 'caip-x', + data: { foo: 'bar' }, + }, + ]); + }); + }); +}); From 65c9d6839fd84a8935b7c80b03d7711e9986ac44 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 5 Jun 2024 16:18:05 -0700 Subject: [PATCH 11/37] WIP createCaipStream spec --- shared/modules/caip-stream.test.ts | 27 ++++++++++++++++++++++++++- shared/modules/caip-stream.ts | 6 ++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index e33102cd415d..8cc18a773660 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -1,4 +1,4 @@ -import { Duplex } from 'stream'; +import { Duplex } from 'readable-stream'; import { deferredPromise } from '../../app/scripts/lib/util'; import { createCaipStream, @@ -134,4 +134,29 @@ describe('CAIP Stream', () => { ]); }); }); + + describe('createCaipStream', () => { + it('pipes a caip-x message from source stream to the substream as a multiplexed `metamask-provider` message', async () => { + const sourceStreamChunks: unknown[] = [] + const sourceStream = new Duplex({ + objectMode: true, + read: () => undefined, + write: (chunk, _encoding, callback) => { + sourceStreamChunks.push(chunk) + callback() + } + }) + + const providerStream = createCaipStream(sourceStream) + const providerStreamChunks: unknown[] = []; + providerStream.on('data', (chunk: unknown) => { + providerStreamChunks.push(chunk); + }); + + await writeToStream(sourceStream, {type: 'caip-x', data: {foo: 'bar'}}) + + expect(sourceStreamChunks).toStrictEqual([{type: 'caip-x', data: {foo: 'bar'}}]) + expect(providerStreamChunks).toStrictEqual([{name: 'metamask-provider', data: {foo: 'bar'}}]) + }) + }) }); diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index 7b9cdc25b9a8..89113e12720e 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -10,7 +10,9 @@ export class SplitStream extends Duplex { this.substream = substream ?? new SplitStream(this); } - _read() {} + _read() { + return undefined; + } _write( value: unknown, @@ -62,7 +64,7 @@ export class MultiplexToCaipStream extends Transform { } } -export const createCaipStream = (portStream: PortStream): Duplex => { +export const createCaipStream = (portStream: Duplex): Duplex => { const splitStream = new SplitStream(); const caipToMultiplexStream = new CaipToMultiplexStream(); const multiplexToCaipStream = new MultiplexToCaipStream(); From c8b66b5d272a95db21b357086b0d65846485ff55 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 10:06:30 -0700 Subject: [PATCH 12/37] add createCaipStream specs --- shared/modules/caip-stream.test.ts | 38 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 8cc18a773660..17e2a21513c9 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -1,4 +1,4 @@ -import { Duplex } from 'readable-stream'; +import { Duplex, PassThrough } from 'readable-stream'; import { deferredPromise } from '../../app/scripts/lib/util'; import { createCaipStream, @@ -137,15 +137,11 @@ describe('CAIP Stream', () => { describe('createCaipStream', () => { it('pipes a caip-x message from source stream to the substream as a multiplexed `metamask-provider` message', async () => { + const sourceStream = new PassThrough({objectMode: true}) const sourceStreamChunks: unknown[] = [] - const sourceStream = new Duplex({ - objectMode: true, - read: () => undefined, - write: (chunk, _encoding, callback) => { - sourceStreamChunks.push(chunk) - callback() - } - }) + sourceStream.on('data', (chunk: unknown) => { + sourceStreamChunks.push(chunk); + }); const providerStream = createCaipStream(sourceStream) const providerStreamChunks: unknown[] = []; @@ -158,5 +154,29 @@ describe('CAIP Stream', () => { expect(sourceStreamChunks).toStrictEqual([{type: 'caip-x', data: {foo: 'bar'}}]) expect(providerStreamChunks).toStrictEqual([{name: 'metamask-provider', data: {foo: 'bar'}}]) }) + + it('pipes a multiplexed `metamask-provider` message from the substream to the source stream as a caip-x message', async () => { + // using a SplitStream here instead of PassThrough to prevent a loop + // when sourceStream gets written to at the end of the CAIP pipeline + const sourceStream = new SplitStream() + const sourceStreamChunks: unknown[] = [] + sourceStream.substream.on('data', (chunk: unknown) => { + sourceStreamChunks.push(chunk); + }); + + const providerStream = createCaipStream(sourceStream) + const providerStreamChunks: unknown[] = []; + providerStream.on('data', (chunk: unknown) => { + providerStreamChunks.push(chunk); + }); + + await writeToStream(providerStream, {name: 'metamask-provider', data: {foo: 'bar'}}) + + await new Promise(resolve => {setTimeout(resolve, 1000)}) + + // Note that it's not possible to verify the output side of the internal SplitStream + // instantiated inside createCaipStream as only the substream is actually exported + expect(sourceStreamChunks).toStrictEqual([{type: 'caip-x', data: {foo: 'bar'}}]) + }) }) }); From bbb33aee62d07169157e6c3fc76fd6baec146b0b Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 10:13:48 -0700 Subject: [PATCH 13/37] lint --- shared/modules/caip-stream.test.ts | 42 ++++++++++++++++++------------ shared/modules/caip-stream.ts | 1 - 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 17e2a21513c9..a964caaeb459 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -137,46 +137,56 @@ describe('CAIP Stream', () => { describe('createCaipStream', () => { it('pipes a caip-x message from source stream to the substream as a multiplexed `metamask-provider` message', async () => { - const sourceStream = new PassThrough({objectMode: true}) - const sourceStreamChunks: unknown[] = [] + const sourceStream = new PassThrough({ objectMode: true }); + const sourceStreamChunks: unknown[] = []; sourceStream.on('data', (chunk: unknown) => { sourceStreamChunks.push(chunk); }); - const providerStream = createCaipStream(sourceStream) + const providerStream = createCaipStream(sourceStream); const providerStreamChunks: unknown[] = []; providerStream.on('data', (chunk: unknown) => { providerStreamChunks.push(chunk); }); - await writeToStream(sourceStream, {type: 'caip-x', data: {foo: 'bar'}}) + await writeToStream(sourceStream, { + type: 'caip-x', + data: { foo: 'bar' }, + }); - expect(sourceStreamChunks).toStrictEqual([{type: 'caip-x', data: {foo: 'bar'}}]) - expect(providerStreamChunks).toStrictEqual([{name: 'metamask-provider', data: {foo: 'bar'}}]) - }) + expect(sourceStreamChunks).toStrictEqual([ + { type: 'caip-x', data: { foo: 'bar' } }, + ]); + expect(providerStreamChunks).toStrictEqual([ + { name: 'metamask-provider', data: { foo: 'bar' } }, + ]); + }); it('pipes a multiplexed `metamask-provider` message from the substream to the source stream as a caip-x message', async () => { // using a SplitStream here instead of PassThrough to prevent a loop // when sourceStream gets written to at the end of the CAIP pipeline - const sourceStream = new SplitStream() - const sourceStreamChunks: unknown[] = [] + const sourceStream = new SplitStream(); + const sourceStreamChunks: unknown[] = []; sourceStream.substream.on('data', (chunk: unknown) => { sourceStreamChunks.push(chunk); }); - const providerStream = createCaipStream(sourceStream) + const providerStream = createCaipStream(sourceStream); const providerStreamChunks: unknown[] = []; providerStream.on('data', (chunk: unknown) => { providerStreamChunks.push(chunk); }); - await writeToStream(providerStream, {name: 'metamask-provider', data: {foo: 'bar'}}) - - await new Promise(resolve => {setTimeout(resolve, 1000)}) + await writeToStream(providerStream, { + name: 'metamask-provider', + data: { foo: 'bar' }, + }); // Note that it's not possible to verify the output side of the internal SplitStream // instantiated inside createCaipStream as only the substream is actually exported - expect(sourceStreamChunks).toStrictEqual([{type: 'caip-x', data: {foo: 'bar'}}]) - }) - }) + expect(sourceStreamChunks).toStrictEqual([ + { type: 'caip-x', data: { foo: 'bar' } }, + ]); + }); + }); }); diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index 89113e12720e..97626b7a2b83 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -1,5 +1,4 @@ import { isObject } from '@metamask/utils'; -import PortStream from 'extension-port-stream'; import { Transform, pipeline, Duplex } from 'readable-stream'; export class SplitStream extends Duplex { From 040d300194b435058be2a2486226d9f54092cc14 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 10:35:27 -0700 Subject: [PATCH 14/37] add BARAD_DUR flag --- app/scripts/background.js | 5 +---- builds.yml | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index c5ca9cb341d5..f906aa83a96c 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -212,12 +212,9 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { // This is set in `setupController`, which is called as part of initialization const port = args[0]; - if (port.sender.tab?.id) { - // unwrap envelope here - console.log('onConnectExternal inpage', ...args); + if (port.sender.tab?.id && process.env.BARAD_DUR) { connectExternalDapp(...args); } else { - console.log('onConnectExternal extension', ...args); connectExternalLegacy(...args); } }); diff --git a/builds.yml b/builds.yml index 0f348911f493..c0c61d55849a 100644 --- a/builds.yml +++ b/builds.yml @@ -298,6 +298,8 @@ env: - MULTICHAIN: '' # Determines if feature flagged Multichain Transactions should be used - TRANSACTION_MULTICHAIN: '' + # Determines if Barad Dur features should be used + - BARAD_DUR: '' # Determines if feature flagged Chain permissions - CHAIN_PERMISSIONS: '' # Enables use of test gas fee flow to debug gas fee estimation From 2acb7193448e0492ca4eb6c2ad60a4ef929e4914 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 11:09:10 -0700 Subject: [PATCH 15/37] dry background trackDappView --- app/scripts/background.js | 100 ++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f906aa83a96c..9a8dd3df2097 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -525,6 +525,47 @@ function emitDappViewedMetricEvent( } } +/** + * Track dapp connection when loaded and permissioned + * + * @param {Port} remotePort - The port provided by a new context. + * @param {object} preferencesController - Preference Controller to get total created accounts + * @param {object} permissionController - Permission Controller to check if origin is permitted + */ +function trackDappView( + remotePort, + preferencesController, + permissionController, +) { + if (!remotePort.sender || !remotePort.sender.tab || !remotePort.sender.url) { + return; + } + const tabId = remotePort.sender.tab.id; + const url = new URL(remotePort.sender.url); + const { origin } = url; + + // store the orgin to corresponding tab so it can provide infor for onActivated listener + if (!Object.keys(tabOriginMapping).includes(tabId)) { + tabOriginMapping[tabId] = origin; + } + const connectSitePermissions = permissionController.state.subjects[origin]; + // when the dapp is not connected, connectSitePermissions is undefined + const isConnectedToDapp = connectSitePermissions !== undefined; + // when open a new tab, this event will trigger twice, only 2nd time is with dapp loaded + const isTabLoaded = remotePort.sender.tab.title !== 'New Tab'; + + // *** Emit DappViewed metric event when *** + // - refresh the dapp + // - open dapp in a new tab + if (isConnectedToDapp && isTabLoaded) { + emitDappViewedMetricEvent( + origin, + connectSitePermissions, + preferencesController, + ); + } +} + /** * Initializes the MetaMask Controller with any initial state and default language. * Configures platform-specific error reporting strategy. @@ -742,27 +783,11 @@ export function setupController( const url = new URL(remotePort.sender.url); const { origin } = url; - // store the orgin to corresponding tab so it can provide infor for onActivated listener - if (!Object.keys(tabOriginMapping).includes(tabId)) { - tabOriginMapping[tabId] = origin; - } - const connectSitePermissions = - controller.permissionController.state.subjects[origin]; - // when the dapp is not connected, connectSitePermissions is undefined - const isConnectedToDapp = connectSitePermissions !== undefined; - // when open a new tab, this event will trigger twice, only 2nd time is with dapp loaded - const isTabLoaded = remotePort.sender.tab.title !== 'New Tab'; - - // *** Emit DappViewed metric event when *** - // - refresh the dapp - // - open dapp in a new tab - if (isConnectedToDapp && isTabLoaded) { - emitDappViewedMetricEvent( - origin, - connectSitePermissions, - controller.preferencesController, - ); - } + trackDappView( + remotePort, + controller.preferencesController, + controller.permissionController, + ); remotePort.onMessage.addListener((msg) => { if ( @@ -808,30 +833,19 @@ export function setupController( const url = new URL(remotePort.sender.url); const { origin } = url; - // store the orgin to corresponding tab so it can provide infor for onActivated listener - if (!Object.keys(tabOriginMapping).includes(tabId)) { - tabOriginMapping[tabId] = origin; - } - // const connectSitePermissions = - // controller.permissionController.state.subjects[origin]; - // // when the dapp is not connected, connectSitePermissions is undefined - // const isConnectedToDapp = connectSitePermissions !== undefined; - // // when open a new tab, this event will trigger twice, only 2nd time is with dapp loaded - // const isTabLoaded = remotePort.sender.tab.title !== 'New Tab'; - - // // *** Emit DappViewed metric event when *** - // // - refresh the dapp - // // - open dapp in a new tab - // if (isConnectedToDapp && isTabLoaded) { - // emitDappViewedMetricEvent( - // origin, - // connectSitePermissions, - // controller.preferencesController, - // ); - // } + trackDappView( + remotePort, + controller.preferencesController, + controller.permissionController, + ); + // TODO: remove this when we separate the legacy and multichain rpc pipelines remotePort.onMessage.addListener((msg) => { - if (msg.data && msg.data.method === MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS) { + if ( + msg.type === 'caip-x' && + msg.data && + msg.data.method === MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS + ) { requestAccountTabIds[origin] = tabId; } }); From 97925942974bf1067d3dfef284614a3b55712c4a Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 11:25:27 -0700 Subject: [PATCH 16/37] move externally_connectable manifest wildcard behind BARAD_DUR --- app/manifest/v2/_barad_dur.json | 6 ++++++ app/manifest/v2/chrome.json | 2 +- app/manifest/v3/_barad_dur.json | 6 ++++++ app/manifest/v3/chrome.json | 2 +- development/build/manifest.js | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 app/manifest/v2/_barad_dur.json create mode 100644 app/manifest/v3/_barad_dur.json diff --git a/app/manifest/v2/_barad_dur.json b/app/manifest/v2/_barad_dur.json new file mode 100644 index 000000000000..304ebf8c4a24 --- /dev/null +++ b/app/manifest/v2/_barad_dur.json @@ -0,0 +1,6 @@ +{ + "externally_connectable": { + "matches": ["http://*/*", "https://*/*"], + "ids": ["*"] + } +} diff --git a/app/manifest/v2/chrome.json b/app/manifest/v2/chrome.json index 8dfcaa0c8c48..e3d68547824a 100644 --- a/app/manifest/v2/chrome.json +++ b/app/manifest/v2/chrome.json @@ -1,7 +1,7 @@ { "content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'none'", "externally_connectable": { - "matches": ["file://*/*", "http://*/*", "https://*/*"], + "matches": ["https://metamask.io/*"], "ids": ["*"] }, "minimum_chrome_version": "89" diff --git a/app/manifest/v3/_barad_dur.json b/app/manifest/v3/_barad_dur.json new file mode 100644 index 000000000000..304ebf8c4a24 --- /dev/null +++ b/app/manifest/v3/_barad_dur.json @@ -0,0 +1,6 @@ +{ + "externally_connectable": { + "matches": ["http://*/*", "https://*/*"], + "ids": ["*"] + } +} diff --git a/app/manifest/v3/chrome.json b/app/manifest/v3/chrome.json index 4d0d1a8f883f..79656e26f0f9 100644 --- a/app/manifest/v3/chrome.json +++ b/app/manifest/v3/chrome.json @@ -3,7 +3,7 @@ "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';" }, "externally_connectable": { - "matches": ["file://*/*", "http://*/*", "https://*/*"], + "matches": ["https://metamask.io/*"], "ids": ["*"] }, "minimum_chrome_version": "89" diff --git a/development/build/manifest.js b/development/build/manifest.js index c15107564c9a..4391f87a5365 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -9,6 +9,9 @@ const IS_MV3_ENABLED = const baseManifest = IS_MV3_ENABLED ? require('../../app/manifest/v3/_base.json') : require('../../app/manifest/v2/_base.json'); +const baradDurManifest = IS_MV3_ENABLED +? require('../../app/manifest/v3/_barad_dur.json') +: require('../../app/manifest/v2/_barad_dur.json'); const { loadBuildTypesConfig } = require('../lib/build-type'); const { TASKS, ENVIRONMENT } = require('./constants'); @@ -41,6 +44,7 @@ function createManifestTasks({ ); const result = mergeWith( cloneDeep(baseManifest), + process.env.BARAD_DUR ? cloneDeep(baradDurManifest) : {}, platformModifications, browserVersionMap[platform], await getBuildModifications(buildType, platform), From 73726049a0584290840119aea4effa03d5f92bc2 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 6 Jun 2024 11:56:21 -0700 Subject: [PATCH 17/37] jsdoc --- development/build/manifest.js | 4 ++-- shared/modules/caip-stream.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/development/build/manifest.js b/development/build/manifest.js index 4391f87a5365..d0042af75c67 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -10,8 +10,8 @@ const baseManifest = IS_MV3_ENABLED ? require('../../app/manifest/v3/_base.json') : require('../../app/manifest/v2/_base.json'); const baradDurManifest = IS_MV3_ENABLED -? require('../../app/manifest/v3/_barad_dur.json') -: require('../../app/manifest/v2/_barad_dur.json'); + ? require('../../app/manifest/v3/_barad_dur.json') + : require('../../app/manifest/v2/_barad_dur.json'); const { loadBuildTypesConfig } = require('../lib/build-type'); const { TASKS, ENVIRONMENT } = require('./constants'); diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index 97626b7a2b83..6bad1ac9f68a 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -63,6 +63,18 @@ export class MultiplexToCaipStream extends Transform { } } +/** + * Creates a pipeline using a port stream meant to be consumed by the JSON-RPC engine: + * - accepts only incoming CAIP messages intended for evm providers from the port stream + * - translates those incoming messages into the internal multiplexed format for 'metamask-provider' + * - writes these messages to a new stream that the JSON-RPC engine should operate off + * - accepts only outgoing messages in the internal multiplexed format for 'metamask-provider' from this new stream + * - translates those outgoing messages back to the CAIP message format + * - writes these messages back to the port stream + * + * @param portStream - The source and sink duplex stream + * @returns a new duplex stream that should be operated on instead of the original portStream + */ export const createCaipStream = (portStream: Duplex): Duplex => { const splitStream = new SplitStream(); const caipToMultiplexStream = new CaipToMultiplexStream(); From b82888b091eed9b0035f604e706018972ff4b386 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 14:25:41 -0700 Subject: [PATCH 18/37] restore inpage --- app/scripts/inpage.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 4595581b7df3..92ae8d28658b 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -32,14 +32,13 @@ cleanContextForImports(); /* eslint-disable import/first */ import log from 'loglevel'; -import { v4 as uuid } from 'uuid'; -import PortStream from 'extension-port-stream'; +import { WindowPostMessageStream } from '@metamask/post-message-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; -import { createCaipStream } from '../../shared/modules/caip-stream'; // contexts -const EXTENSION_ID = 'nonfpcflonapegmnfeafnddgdniflbnk'; +const CONTENT_SCRIPT = 'metamask-contentscript'; +const INPAGE = 'metamask-inpage'; restoreContextAfterImports(); @@ -51,19 +50,14 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn'); if (shouldInjectProvider()) { // setup background connection - const extensionPort = chrome.runtime.connect(EXTENSION_ID); - const portStream = new PortStream(extensionPort); - const connectionStream = createCaipStream(portStream); + const metamaskStream = new WindowPostMessageStream({ + name: INPAGE, + target: CONTENT_SCRIPT, + }); initializeProvider({ - connectionStream, + connectionStream: metamaskStream, logger: log, shouldShimWeb3: true, - providerInfo: { - uuid: uuid(), - name: process.env.METAMASK_BUILD_NAME, - icon: process.env.METAMASK_BUILD_ICON, - rdns: process.env.METAMASK_BUILD_APP_ID, - }, }); } From 64ca6550343e84657c746b91977137b498eabb57 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 14:36:48 -0700 Subject: [PATCH 19/37] Move caip stream closer to provider. Replace caip<->multiplex transform with caip wrap/unwrap --- app/scripts/background.js | 9 ++-- app/scripts/metamask-controller.js | 30 ++++++++++++- shared/modules/caip-stream.ts | 68 ++++++++++-------------------- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 9a8dd3df2097..9d3e23b63a5f 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -43,7 +43,6 @@ import { checkForLastErrorAndLog } from '../../shared/modules/browser-runtime.ut import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { maskObject } from '../../shared/modules/object.utils'; import { FIXTURE_STATE_METADATA_VERSION } from '../../test/e2e/default-fixture'; -import { createCaipStream } from '../../shared/modules/caip-stream'; import migrations from './migrations'; import Migrator from './lib/migrator'; import ExtensionPlatform from './platforms/extension'; @@ -816,7 +815,7 @@ export function setupController( const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - controller.setupUntrustedCommunication({ + controller.setupUntrustedCommunicationLegacy({ connectionStream: portStream, sender: remotePort.sender, }); @@ -854,10 +853,8 @@ export function setupController( const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - const connectionStream = createCaipStream(portStream); - - controller.setupUntrustedCommunication({ - connectionStream, + controller.setupUntrustedCommunicationCaip({ + connectionStream: portStream, sender: remotePort.sender, }); }; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fdfddfd3689b..033d4570db48 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -219,6 +219,7 @@ import { getSmartTransactionsOptInStatus, getCurrentChainSupportsSmartTransactions, } from '../../shared/modules/selectors'; +import { createCaipStream } from '../../shared/modules/caip-stream'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) handleMMITransactionUpdate, @@ -4812,7 +4813,7 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender of the messages on this stream. * @param {string} [options.subjectType] - The type of the sender, i.e. subject. */ - setupUntrustedCommunication({ connectionStream, sender, subjectType }) { + setupUntrustedCommunicationLegacy({ connectionStream, sender, subjectType }) { const { completedOnboarding } = this.onboardingController.store.getState(); const { usePhishDetect } = this.preferencesController.store.getState(); @@ -4860,6 +4861,31 @@ export default class MetamaskController extends EventEmitter { } } + /** + * Used to create a CAIP stream for connecting to an untrusted context. + * + * @param options - Options bag. + * @param {ReadableStream} options.connectionStream - The Duplex stream to connect to. + * @param {MessageSender | SnapSender} options.sender - The sender of the messages on this stream. + * @param {string} [options.subjectType] - The type of the sender, i.e. subject. + */ + + setupUntrustedCommunicationCaip({ connectionStream, sender, subjectType }) { + let _subjectType; + if (subjectType) { + _subjectType = subjectType; + } else if (sender.id && sender.id !== this.extension.runtime.id) { + _subjectType = SubjectType.Extension; + } else { + _subjectType = SubjectType.Website; + } + + const caipStream = createCaipStream(connectionStream); + + // messages between subject and background + this.setupProviderConnection(caipStream, sender, _subjectType); + } + /** * Used to create a multiplexed stream for connecting to a trusted context, * like our own user interfaces, which have the provider APIs, but also @@ -5056,7 +5082,7 @@ export default class MetamaskController extends EventEmitter { * @param connectionStream */ setupSnapProvider(snapId, connectionStream) { - this.setupUntrustedCommunication({ + this.setupUntrustedCommunicationLegacy({ connectionStream, sender: { snapId }, subjectType: SubjectType.Snap, diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index 6bad1ac9f68a..d37e625d2292 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -1,12 +1,12 @@ import { isObject } from '@metamask/utils'; -import { Transform, pipeline, Duplex } from 'readable-stream'; +import { pipeline, Duplex } from 'readable-stream'; -export class SplitStream extends Duplex { - substream: Duplex; +class Substream extends Duplex { + parent: Duplex; - constructor(substream?: SplitStream) { + constructor(parent: Duplex) { super({ objectMode: true }); - this.substream = substream ?? new SplitStream(this); + this.parent = parent; } _read() { @@ -18,34 +18,24 @@ export class SplitStream extends Duplex { _encoding: BufferEncoding, callback: (error?: Error | null) => void, ) { - this.substream.push(value); + this.parent.push({ + type: 'caip-x', + data: value, + }); callback(); } } -export class CaipToMultiplexStream extends Transform { +export class CaipStream extends Duplex { + substream: Duplex; + constructor() { super({ objectMode: true }); + this.substream = new Substream(this); } - _write( - value: unknown, - _encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ) { - if (isObject(value) && value.type === 'caip-x') { - this.push({ - name: 'metamask-provider', - data: value.data, - }); - } - callback(); - } -} - -export class MultiplexToCaipStream extends Transform { - constructor() { - super({ objectMode: true }); + _read() { + return undefined; } _write( @@ -53,11 +43,8 @@ export class MultiplexToCaipStream extends Transform { _encoding: BufferEncoding, callback: (error?: Error | null) => void, ) { - if (isObject(value) && value.name === 'metamask-provider') { - this.push({ - type: 'caip-x', - data: value.data, - }); + if (isObject(value) && value.type === 'caip-x') { + this.substream.push(value.data); } callback(); } @@ -66,28 +53,19 @@ export class MultiplexToCaipStream extends Transform { /** * Creates a pipeline using a port stream meant to be consumed by the JSON-RPC engine: * - accepts only incoming CAIP messages intended for evm providers from the port stream - * - translates those incoming messages into the internal multiplexed format for 'metamask-provider' - * - writes these messages to a new stream that the JSON-RPC engine should operate off - * - accepts only outgoing messages in the internal multiplexed format for 'metamask-provider' from this new stream - * - translates those outgoing messages back to the CAIP message format + * - unwraps these incoming messages into a new stream that the JSON-RPC engine should operate off + * - wraps the outgoing messages from the new stream back into the CAIP message format * - writes these messages back to the port stream * * @param portStream - The source and sink duplex stream * @returns a new duplex stream that should be operated on instead of the original portStream */ export const createCaipStream = (portStream: Duplex): Duplex => { - const splitStream = new SplitStream(); - const caipToMultiplexStream = new CaipToMultiplexStream(); - const multiplexToCaipStream = new MultiplexToCaipStream(); + const caipStream = new CaipStream(); - pipeline( - portStream, - caipToMultiplexStream, - splitStream, - multiplexToCaipStream, - portStream, - (err: Error) => console.log('MetaMask CAIP stream', err), + pipeline(portStream, caipStream, portStream, (err: Error) => + console.log('MetaMask CAIP stream', err), ); - return splitStream.substream; + return caipStream.substream; }; From 29ea68c2c0aa6f740e0cb526c4407a5727e17794 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 14:42:07 -0700 Subject: [PATCH 20/37] Rename connectExternalDapp to connectExternalCaip --- app/scripts/background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 8ab84b09f62f..f72f08ce783e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -179,7 +179,7 @@ const sendReadyMessageToTabs = async () => { // These are set after initialization let connectRemote; let connectExternalLegacy; -let connectExternalDapp; +let connectExternalCaip; browser.runtime.onConnect.addListener(async (...args) => { // Queue up connection attempts here, waiting until after initialization @@ -195,7 +195,7 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { const port = args[0]; if (port.sender.tab?.id && process.env.BARAD_DUR) { - connectExternalDapp(...args); + connectExternalCaip(...args); } else { connectExternalLegacy(...args); } @@ -770,7 +770,7 @@ export function setupController( }); }; - connectExternalDapp = async (remotePort) => { + connectExternalCaip = async (remotePort) => { if (metamaskBlockedPorts.includes(remotePort.name)) { return; } From 64ba9905c8146a5df5a590c3fb9a7817dfd03d33 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 14:45:28 -0700 Subject: [PATCH 21/37] actually restore inpage --- app/scripts/inpage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 92ae8d28658b..d00a0542db03 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -32,6 +32,7 @@ cleanContextForImports(); /* eslint-disable import/first */ import log from 'loglevel'; +import { v4 as uuid } from 'uuid'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; @@ -59,5 +60,11 @@ if (shouldInjectProvider()) { connectionStream: metamaskStream, logger: log, shouldShimWeb3: true, + providerInfo: { + uuid: uuid(), + name: process.env.METAMASK_BUILD_NAME, + icon: process.env.METAMASK_BUILD_ICON, + rdns: process.env.METAMASK_BUILD_APP_ID, + }, }); } From 46bfee780634a67fb6f6b0ffef865c610099f446 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 15:03:16 -0700 Subject: [PATCH 22/37] Fix createCaipStream specs --- shared/modules/caip-stream.test.ts | 184 ++++++----------------------- 1 file changed, 36 insertions(+), 148 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index a964caaeb459..ed05bc1f2916 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -1,153 +1,52 @@ import { Duplex, PassThrough } from 'readable-stream'; import { deferredPromise } from '../../app/scripts/lib/util'; -import { - createCaipStream, - SplitStream, - CaipToMultiplexStream, - MultiplexToCaipStream, -} from './caip-stream'; +import { createCaipStream } from './caip-stream'; const writeToStream = async (stream: Duplex, message: unknown) => { const { promise: isWritten, resolve: writeCallback } = deferredPromise(); - stream.write(message, writeCallback); + stream.write(message, () => writeCallback()); await isWritten; }; -describe('CAIP Stream', () => { - describe('SplitStream', () => { - it('redirects writes from the main stream to the substream', async () => { - const splitStream = new SplitStream(); - - const outerStreamChunks: unknown[] = []; - splitStream.on('data', (chunk: unknown) => { - outerStreamChunks.push(chunk); - }); - - const innerStreamChunks: unknown[] = []; - splitStream.substream.on('data', (chunk: unknown) => { - innerStreamChunks.push(chunk); - }); - - await writeToStream(splitStream, { foo: 'bar' }); - - expect(outerStreamChunks).toStrictEqual([]); - expect(innerStreamChunks).toStrictEqual([{ foo: 'bar' }]); - }); - - it('redirects writes from the substream to the main stream', async () => { - const splitStream = new SplitStream(); - - const outerStreamChunks: unknown[] = []; - splitStream.on('data', (chunk: unknown) => { - outerStreamChunks.push(chunk); - }); - - const innerStreamChunks: unknown[] = []; - splitStream.substream.on('data', (chunk: unknown) => { - innerStreamChunks.push(chunk); - }); - - await writeToStream(splitStream.substream, { foo: 'bar' }); - - expect(outerStreamChunks).toStrictEqual([{ foo: 'bar' }]); - expect(innerStreamChunks).toStrictEqual([]); - }); +const onData = (stream: Duplex): unknown[] => { + const chunks: unknown[] = []; + stream.on('data', (chunk: unknown) => { + chunks.push(chunk); }); - describe('CaipToMultiplexStream', () => { - it('drops non caip-x messages', async () => { - const caipToMultiplexStream = new CaipToMultiplexStream(); - - const streamChunks: unknown[] = []; - caipToMultiplexStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk); - }); - - await writeToStream(caipToMultiplexStream, { foo: 'bar' }); - await writeToStream(caipToMultiplexStream, { - type: 'caip-wrong', - data: { foo: 'bar' }, - }); - - expect(streamChunks).toStrictEqual([]); - }); - - it('rewraps caip-x messages into multiplexed `metamask-provider` messages', async () => { - const caipToMultiplexStream = new CaipToMultiplexStream(); - - const streamChunks: unknown[] = []; - caipToMultiplexStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk); - }); - - await writeToStream(caipToMultiplexStream, { - type: 'caip-x', - data: { foo: 'bar' }, - }); - - expect(streamChunks).toStrictEqual([ - { - name: 'metamask-provider', - data: { foo: 'bar' }, - }, - ]); - }); - }); - - describe('MultiplexToCaipStream', () => { - it('drops non multiplexed `metamask-provider` messages', async () => { - const multiplexToCaipStream = new MultiplexToCaipStream(); - - const streamChunks: unknown[] = []; - multiplexToCaipStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk); - }); - - await writeToStream(multiplexToCaipStream, { foo: 'bar' }); - await writeToStream(multiplexToCaipStream, { - name: 'wrong-multiplex', - data: { foo: 'bar' }, - }); - - expect(streamChunks).toStrictEqual([]); - }); + return chunks; +}; - it('rewraps multiplexed `metamask-provider` messages into caip-x messages', async () => { - const multiplexToCaipStream = new MultiplexToCaipStream(); +class MockStream extends Duplex { + chunks: unknown[] = []; - const streamChunks: unknown[] = []; - multiplexToCaipStream.on('data', (chunk: unknown) => { - streamChunks.push(chunk); - }); + constructor() { + super({ objectMode: true }); + } - await writeToStream(multiplexToCaipStream, { - name: 'metamask-provider', - data: { foo: 'bar' }, - }); + _read() { + return undefined; + } - expect(streamChunks).toStrictEqual([ - { - type: 'caip-x', - data: { foo: 'bar' }, - }, - ]); - }); - }); + _write( + value: unknown, + _encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + this.chunks.push(value); + callback(); + } +} +describe('CAIP Stream', () => { describe('createCaipStream', () => { - it('pipes a caip-x message from source stream to the substream as a multiplexed `metamask-provider` message', async () => { + it('pipes and unwraps a caip-x message from source stream to the substream', async () => { const sourceStream = new PassThrough({ objectMode: true }); - const sourceStreamChunks: unknown[] = []; - sourceStream.on('data', (chunk: unknown) => { - sourceStreamChunks.push(chunk); - }); + const sourceStreamChunks = onData(sourceStream); const providerStream = createCaipStream(sourceStream); - const providerStreamChunks: unknown[] = []; - providerStream.on('data', (chunk: unknown) => { - providerStreamChunks.push(chunk); - }); + const providerStreamChunks = onData(providerStream); await writeToStream(sourceStream, { type: 'caip-x', @@ -157,34 +56,23 @@ describe('CAIP Stream', () => { expect(sourceStreamChunks).toStrictEqual([ { type: 'caip-x', data: { foo: 'bar' } }, ]); - expect(providerStreamChunks).toStrictEqual([ - { name: 'metamask-provider', data: { foo: 'bar' } }, - ]); + expect(providerStreamChunks).toStrictEqual([{ foo: 'bar' }]); }); - it('pipes a multiplexed `metamask-provider` message from the substream to the source stream as a caip-x message', async () => { - // using a SplitStream here instead of PassThrough to prevent a loop - // when sourceStream gets written to at the end of the CAIP pipeline - const sourceStream = new SplitStream(); - const sourceStreamChunks: unknown[] = []; - sourceStream.substream.on('data', (chunk: unknown) => { - sourceStreamChunks.push(chunk); - }); + it('pipes and wraps a message from the substream to the source stream', async () => { + // using a fake stream here instead of PassThrough to prevent a loop + // when sourceStream gets written back to at the end of the CAIP pipeline + const sourceStream = new MockStream(); const providerStream = createCaipStream(sourceStream); - const providerStreamChunks: unknown[] = []; - providerStream.on('data', (chunk: unknown) => { - providerStreamChunks.push(chunk); - }); await writeToStream(providerStream, { - name: 'metamask-provider', - data: { foo: 'bar' }, + foo: 'bar', }); // Note that it's not possible to verify the output side of the internal SplitStream // instantiated inside createCaipStream as only the substream is actually exported - expect(sourceStreamChunks).toStrictEqual([ + expect(sourceStream.chunks).toStrictEqual([ { type: 'caip-x', data: { foo: 'bar' } }, ]); }); From e24ef63c54d4c18beaf1ddcc7d979cbfd6939e31 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 11 Jun 2024 15:44:02 -0700 Subject: [PATCH 23/37] use createDeferredPromise instead --- shared/modules/caip-stream.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index ed05bc1f2916..650bf19bf31b 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -1,9 +1,10 @@ import { Duplex, PassThrough } from 'readable-stream'; -import { deferredPromise } from '../../app/scripts/lib/util'; +import { createDeferredPromise } from '@metamask/utils'; import { createCaipStream } from './caip-stream'; const writeToStream = async (stream: Duplex, message: unknown) => { - const { promise: isWritten, resolve: writeCallback } = deferredPromise(); + const { promise: isWritten, resolve: writeCallback } = + createDeferredPromise(); stream.write(message, () => writeCallback()); await isWritten; From 379ce8af60b82f180d8767b0ddede041d1f480b5 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 12 Jun 2024 13:14:04 -0700 Subject: [PATCH 24/37] rename onData to readFromStream --- shared/modules/caip-stream.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 650bf19bf31b..17febf1fcbae 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -10,7 +10,7 @@ const writeToStream = async (stream: Duplex, message: unknown) => { await isWritten; }; -const onData = (stream: Duplex): unknown[] => { +export const readFromStream = (stream: Duplex): unknown[] => { const chunks: unknown[] = []; stream.on('data', (chunk: unknown) => { chunks.push(chunk); @@ -44,10 +44,10 @@ describe('CAIP Stream', () => { describe('createCaipStream', () => { it('pipes and unwraps a caip-x message from source stream to the substream', async () => { const sourceStream = new PassThrough({ objectMode: true }); - const sourceStreamChunks = onData(sourceStream); + const sourceStreamChunks = readFromStream(sourceStream); const providerStream = createCaipStream(sourceStream); - const providerStreamChunks = onData(providerStream); + const providerStreamChunks = readFromStream(providerStream); await writeToStream(sourceStream, { type: 'caip-x', From cd3dd0293625f785d7d2f8a07609c1dfbcd12101 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 12 Jun 2024 16:08:12 -0700 Subject: [PATCH 25/37] fix method names. add setupUntrustedCommunicationCaip --- app/scripts/metamask-controller.test.js | 143 ++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 11 deletions(-) diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 4c66e3bf8fac..6d6d6e0d6958 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -3,7 +3,7 @@ */ import { cloneDeep } from 'lodash'; import nock from 'nock'; -import { obj as createThoughStream } from 'through2'; +import { obj as createThroughStream } from 'through2'; import EthQuery from '@metamask/eth-query'; import { wordlist as englishWordlist } from '@metamask/scure-bip39/dist/wordlists/english'; import { @@ -1102,7 +1102,7 @@ describe('MetaMaskController', () => { }); }); - describe('#setupUntrustedCommunication', () => { + describe('#setupUntrustedCommunicationLegacy', () => { const mockTxParams = { from: TEST_ADDRESS }; beforeEach(() => { @@ -1127,7 +1127,7 @@ describe('MetaMaskController', () => { }; const { promise, resolve } = deferredPromise(); - const streamTest = createThoughStream((chunk, _, cb) => { + const streamTest = createThroughStream((chunk, _, cb) => { if (chunk.name !== 'phishing') { cb(); return; @@ -1139,7 +1139,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunication({ + metamaskController.setupUntrustedCommunicationLegacy({ connectionStream: streamTest, sender: phishingMessageSender, }); @@ -1163,7 +1163,7 @@ describe('MetaMaskController', () => { }; const { resolve } = deferredPromise(); - const streamTest = createThoughStream((chunk, _, cb) => { + const streamTest = createThroughStream((chunk, _, cb) => { if (chunk.name !== 'phishing') { cb(); return; @@ -1175,7 +1175,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunication({ + metamaskController.setupUntrustedCommunicationLegacy({ connectionStream: streamTest, sender: phishingMessageSender, }); @@ -1195,7 +1195,7 @@ describe('MetaMaskController', () => { url: 'http://mycrypto.com', tab: { id: 456 }, }; - const streamTest = createThoughStream((chunk, _, cb) => { + const streamTest = createThroughStream((chunk, _, cb) => { if (chunk.data && chunk.data.method) { cb(null, chunk); return; @@ -1203,7 +1203,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunication({ + metamaskController.setupUntrustedCommunicationLegacy({ connectionStream: streamTest, sender: messageSender, }); @@ -1246,7 +1246,7 @@ describe('MetaMaskController', () => { const messageSender = { url: 'http://mycrypto.com', }; - const streamTest = createThoughStream((chunk, _, cb) => { + const streamTest = createThroughStream((chunk, _, cb) => { if (chunk.data && chunk.data.method) { cb(null, chunk); return; @@ -1254,7 +1254,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunication({ + metamaskController.setupUntrustedCommunicationLegacy({ connectionStream: streamTest, sender: messageSender, }); @@ -1287,6 +1287,127 @@ describe('MetaMaskController', () => { ); }); }); + + it.todo('should only process `metamask-provider` multiplex formatted messages'); + }); + + describe('#setupUntrustedCommunicationCaip', () => { + const mockTxParams = { from: TEST_ADDRESS }; + + beforeEach(() => { + initializeMockMiddlewareLog(); + metamaskController.preferencesController.setSecurityAlertsEnabled( + false, + ); + jest + .spyOn(metamaskController.onboardingController.store, 'getState') + .mockReturnValue({ completedOnboarding: true }); + metamaskController.preferencesController.setUsePhishDetect(true); + }); + + afterAll(() => { + tearDownMockMiddlewareLog(); + }); + + it('adds a tabId, origin and networkClient to requests', async () => { + const messageSender = { + url: 'http://mycrypto.com', + tab: { id: 456 }, + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationCaip({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + id: 1999133338649204, + jsonrpc: '2.0', + params: [{ ...mockTxParams }], + method: 'eth_sendTransaction', + }; + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: message, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'origin', + 'http://mycrypto.com', + ); + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'tabId', + 456, + ); + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'networkClientId', + 'networkConfigurationId1', + ); + resolve(); + }); + }, + ); + }); + }); + + it('should add only origin to request if tabId not provided', async () => { + const messageSender = { + url: 'http://mycrypto.com', + }; + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunicationCaip({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + id: 1999133338649204, + jsonrpc: '2.0', + params: [{ ...mockTxParams }], + method: 'eth_sendTransaction', + }; + await new Promise((resolve) => { + streamTest.write( + { + type: 'caip-x', + data: message, + }, + null, + () => { + setTimeout(() => { + expect(loggerMiddlewareMock.requests[0]).not.toHaveProperty( + 'tabId', + ); + expect(loggerMiddlewareMock.requests[0]).toHaveProperty( + 'origin', + 'http://mycrypto.com', + ); + resolve(); + }); + }, + ); + }); + }); + + it.todo('should only process `caip-x` CAIP formatted messages'); }); describe('#setupTrustedCommunication', () => { @@ -1296,7 +1417,7 @@ describe('MetaMaskController', () => { tab: {}, }; const { promise, resolve } = deferredPromise(); - const streamTest = createThoughStream((chunk, _, cb) => { + const streamTest = createThroughStream((chunk, _, cb) => { expect(chunk.name).toStrictEqual('controller'); resolve(); cb(); From cbfd01485793183d935086d93e1b398e83db9f08 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 12 Jun 2024 16:29:07 -0700 Subject: [PATCH 26/37] lint --- app/scripts/metamask-controller.test.js | 4 +++- shared/modules/caip-stream.test.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 6d6d6e0d6958..d36f1c05490a 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -1288,7 +1288,9 @@ describe('MetaMaskController', () => { }); }); - it.todo('should only process `metamask-provider` multiplex formatted messages'); + it.todo( + 'should only process `metamask-provider` multiplex formatted messages', + ); }); describe('#setupUntrustedCommunicationCaip', () => { diff --git a/shared/modules/caip-stream.test.ts b/shared/modules/caip-stream.test.ts index 17febf1fcbae..d97a18bda992 100644 --- a/shared/modules/caip-stream.test.ts +++ b/shared/modules/caip-stream.test.ts @@ -10,7 +10,7 @@ const writeToStream = async (stream: Duplex, message: unknown) => { await isWritten; }; -export const readFromStream = (stream: Duplex): unknown[] => { +const readFromStream = (stream: Duplex): unknown[] => { const chunks: unknown[] = []; stream.on('data', (chunk: unknown) => { chunks.push(chunk); From 3652ab385940251b3d990cdda7e73cc440f0ef48 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 12 Jun 2024 16:42:01 -0700 Subject: [PATCH 27/37] lint --- shared/modules/caip-stream.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/modules/caip-stream.ts b/shared/modules/caip-stream.ts index d37e625d2292..3f13927efc27 100644 --- a/shared/modules/caip-stream.ts +++ b/shared/modules/caip-stream.ts @@ -1,4 +1,6 @@ import { isObject } from '@metamask/utils'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error pipeline() isn't defined as part of @types/readable-stream import { pipeline, Duplex } from 'readable-stream'; class Substream extends Duplex { From 5b667f2384167bd874c34d3aee518db90846bcba Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 14 Jun 2024 15:22:08 -0700 Subject: [PATCH 28/37] Rename connectExternalLegacy to connectExternalExtension --- app/scripts/background.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f72f08ce783e..12f1f776e919 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -178,7 +178,7 @@ const sendReadyMessageToTabs = async () => { // These are set after initialization let connectRemote; -let connectExternalLegacy; +let connectExternalExtension; let connectExternalCaip; browser.runtime.onConnect.addListener(async (...args) => { @@ -197,7 +197,7 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { if (port.sender.tab?.id && process.env.BARAD_DUR) { connectExternalCaip(...args); } else { - connectExternalLegacy(...args); + connectExternalExtension(...args); } }); @@ -756,12 +756,12 @@ export function setupController( } }); } - connectExternalLegacy(remotePort); + connectExternalExtension(remotePort); } }; // communication with page or other extension - connectExternalLegacy = (remotePort) => { + connectExternalExtension = (remotePort) => { const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); controller.setupUntrustedCommunicationLegacy({ @@ -809,7 +809,7 @@ export function setupController( }; if (overrides?.registerConnectListeners) { - overrides.registerConnectListeners(connectRemote, connectExternalLegacy); + overrides.registerConnectListeners(connectRemote, connectExternalExtension); } // From 0c3f000f2150e6fe870c91df20166b3eda9ad436 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 14 Jun 2024 15:44:10 -0700 Subject: [PATCH 29/37] rename _subjectType to inputSubjectType --- app/scripts/metamask-controller.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3f6b4c132292..ab37623d7a80 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4804,13 +4804,13 @@ export default class MetamaskController extends EventEmitter { const { completedOnboarding } = this.onboardingController.store.getState(); const { usePhishDetect } = this.preferencesController.store.getState(); - let _subjectType; + let inputSubjectType; if (subjectType) { - _subjectType = subjectType; + inputSubjectType = subjectType; } else if (sender.id && sender.id !== this.extension.runtime.id) { - _subjectType = SubjectType.Extension; + inputSubjectType = SubjectType.Extension; } else { - _subjectType = SubjectType.Website; + inputSubjectType = SubjectType.Website; } if (usePhishDetect && completedOnboarding && sender.url) { @@ -4838,7 +4838,7 @@ export default class MetamaskController extends EventEmitter { this.setupProviderConnection( mux.createStream('metamask-provider'), sender, - _subjectType, + inputSubjectType, ); // TODO:LegacyProvider: Delete @@ -4858,19 +4858,19 @@ export default class MetamaskController extends EventEmitter { */ setupUntrustedCommunicationCaip({ connectionStream, sender, subjectType }) { - let _subjectType; + let inputSubjectType; if (subjectType) { - _subjectType = subjectType; + inputSubjectType = subjectType; } else if (sender.id && sender.id !== this.extension.runtime.id) { - _subjectType = SubjectType.Extension; + inputSubjectType = SubjectType.Extension; } else { - _subjectType = SubjectType.Website; + inputSubjectType = SubjectType.Website; } const caipStream = createCaipStream(connectionStream); // messages between subject and background - this.setupProviderConnection(caipStream, sender, _subjectType); + this.setupProviderConnection(caipStream, sender, inputSubjectType); } /** From ab7767b911e5f74684cb519e6841cb70e73b9e6e Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 18 Jun 2024 11:54:26 -0700 Subject: [PATCH 30/37] use messenger where possible --- app/scripts/background.js | 98 +++++++++++++++------------------------ 1 file changed, 37 insertions(+), 61 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 12f1f776e919..0a80db9d1fab 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -14,7 +14,7 @@ import debounce from 'debounce-stream'; import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; -import { hasProperty, isObject } from '@metamask/utils'; +import { isObject } from '@metamask/utils'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) import { ApprovalType } from '@metamask/controller-utils'; ///: END:ONLY_INCLUDE_IF @@ -462,59 +462,48 @@ export async function loadStateFromPersistence() { * which should only be tracked only after a user opts into metrics and connected to the dapp * * @param {string} origin - URL of visited dapp - * @param {object} connectSitePermissions - Permission state to get connected accounts - * @param {object} preferencesController - Preference Controller to get total created accounts */ -function emitDappViewedMetricEvent( - origin, - connectSitePermissions, - preferencesController, -) { +function emitDappViewedMetricEvent(origin) { const { metaMetricsId } = controller.metaMetricsController.state; if (!shouldEmitDappViewedEvent(metaMetricsId)) { return; } - // A dapp may have other permissions than eth_accounts. - // Since we are only interested in dapps that use Ethereum accounts, we bail out otherwise. - if (!hasProperty(connectSitePermissions.permissions, 'eth_accounts')) { + const permissions = controller.controllerMessenger.call( + 'PermissionController:getPermissions', + origin, + ); + const numberOfConnectedAccounts = + permissions?.eth_accounts?.caveats[0]?.value.length; + if (!numberOfConnectedAccounts) { return; } - const numberOfTotalAccounts = Object.keys( - preferencesController.store.getState().identities, - ).length; - const connectAccountsCollection = - connectSitePermissions.permissions.eth_accounts.caveats; - if (connectAccountsCollection) { - const numberOfConnectedAccounts = connectAccountsCollection[0].value.length; - controller.metaMetricsController.trackEvent({ - event: MetaMetricsEventName.DappViewed, - category: MetaMetricsEventCategory.InpageProvider, - referrer: { - url: origin, - }, - properties: { - is_first_visit: false, - number_of_accounts: numberOfTotalAccounts, - number_of_accounts_connected: numberOfConnectedAccounts, - }, - }); - } + const preferencesState = controller.controllerMessenger.call( + 'PreferencesController:getState', + ); + const numberOfTotalAccounts = Object.keys(preferencesState.identities).length; + + controller.metaMetricsController.trackEvent({ + event: MetaMetricsEventName.DappViewed, + category: MetaMetricsEventCategory.InpageProvider, + referrer: { + url: origin, + }, + properties: { + is_first_visit: false, + number_of_accounts: numberOfTotalAccounts, + number_of_accounts_connected: numberOfConnectedAccounts, + }, + }); } /** * Track dapp connection when loaded and permissioned * * @param {Port} remotePort - The port provided by a new context. - * @param {object} preferencesController - Preference Controller to get total created accounts - * @param {object} permissionController - Permission Controller to check if origin is permitted */ -function trackDappView( - remotePort, - preferencesController, - permissionController, -) { +function trackDappView(remotePort) { if (!remotePort.sender || !remotePort.sender.tab || !remotePort.sender.url) { return; } @@ -526,9 +515,12 @@ function trackDappView( if (!Object.keys(tabOriginMapping).includes(tabId)) { tabOriginMapping[tabId] = origin; } - const connectSitePermissions = permissionController.state.subjects[origin]; - // when the dapp is not connected, connectSitePermissions is undefined - const isConnectedToDapp = connectSitePermissions !== undefined; + + const isConnectedToDapp = controller.controllerMessenger.call( + 'PermissionController:hasPermissions', + origin, + ); + // when open a new tab, this event will trigger twice, only 2nd time is with dapp loaded const isTabLoaded = remotePort.sender.tab.title !== 'New Tab'; @@ -536,11 +528,7 @@ function trackDappView( // - refresh the dapp // - open dapp in a new tab if (isConnectedToDapp && isTabLoaded) { - emitDappViewedMetricEvent( - origin, - connectSitePermissions, - preferencesController, - ); + emitDappViewedMetricEvent(origin); } } @@ -741,11 +729,7 @@ export function setupController( const url = new URL(remotePort.sender.url); const { origin } = url; - trackDappView( - remotePort, - controller.preferencesController, - controller.permissionController, - ); + trackDappView(remotePort); remotePort.onMessage.addListener((msg) => { if ( @@ -781,11 +765,7 @@ export function setupController( const url = new URL(remotePort.sender.url); const { origin } = url; - trackDappView( - remotePort, - controller.preferencesController, - controller.permissionController, - ); + trackDappView(remotePort); // TODO: remove this when we separate the legacy and multichain rpc pipelines remotePort.onMessage.addListener((msg) => { @@ -1029,11 +1009,7 @@ function onNavigateToTab() { // when the dapp is not connected, connectSitePermissions is undefined const isConnectedToDapp = connectSitePermissions !== undefined; if (isConnectedToDapp) { - emitDappViewedMetricEvent( - currentOrigin, - connectSitePermissions, - controller.preferencesController, - ); + emitDappViewedMetricEvent(currentOrigin); } } } From ccd10abaa4f0ec1ccc34e552b0f3cc9cddb9f2a8 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 18 Jun 2024 12:24:02 -0700 Subject: [PATCH 31/37] Rename setupUntrustedCommunicationLegacy to setupUntrustedCommunicationEip1193 --- app/scripts/background.js | 2 +- app/scripts/metamask-controller.js | 4 ++-- app/scripts/metamask-controller.test.js | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 5098899754bd..2368d46c8ea3 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -764,7 +764,7 @@ export function setupController( connectExternalExtension = (remotePort) => { const portStream = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - controller.setupUntrustedCommunicationLegacy({ + controller.setupUntrustedCommunicationEip1193({ connectionStream: portStream, sender: remotePort.sender, }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8e0d0dfd91af..b6bf48b2f392 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4813,7 +4813,7 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender of the messages on this stream. * @param {string} [options.subjectType] - The type of the sender, i.e. subject. */ - setupUntrustedCommunicationLegacy({ connectionStream, sender, subjectType }) { + setupUntrustedCommunicationEip1193({ connectionStream, sender, subjectType }) { const { completedOnboarding } = this.onboardingController.store.getState(); const { usePhishDetect } = this.preferencesController.store.getState(); @@ -5087,7 +5087,7 @@ export default class MetamaskController extends EventEmitter { * @param connectionStream */ setupSnapProvider(snapId, connectionStream) { - this.setupUntrustedCommunicationLegacy({ + this.setupUntrustedCommunicationEip1193({ connectionStream, sender: { snapId }, subjectType: SubjectType.Snap, diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 1ad5969378d5..c2973b76b5d3 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -1102,7 +1102,7 @@ describe('MetaMaskController', () => { }); }); - describe('#setupUntrustedCommunicationLegacy', () => { + describe('#setupUntrustedCommunicationEip1193', () => { const mockTxParams = { from: TEST_ADDRESS }; beforeEach(() => { @@ -1139,7 +1139,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunicationLegacy({ + metamaskController.setupUntrustedCommunicationEip1193({ connectionStream: streamTest, sender: phishingMessageSender, }); @@ -1175,7 +1175,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunicationLegacy({ + metamaskController.setupUntrustedCommunicationEip1193({ connectionStream: streamTest, sender: phishingMessageSender, }); @@ -1203,7 +1203,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunicationLegacy({ + metamaskController.setupUntrustedCommunicationEip1193({ connectionStream: streamTest, sender: messageSender, }); @@ -1254,7 +1254,7 @@ describe('MetaMaskController', () => { cb(); }); - metamaskController.setupUntrustedCommunicationLegacy({ + metamaskController.setupUntrustedCommunicationEip1193({ connectionStream: streamTest, sender: messageSender, }); From 2eeac73a58dfa423a1ac97a62cd984ccf00bf999 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 18 Jun 2024 13:21:18 -0700 Subject: [PATCH 32/37] lint --- app/scripts/metamask-controller.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b6bf48b2f392..ea2790fc873b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4813,7 +4813,11 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender of the messages on this stream. * @param {string} [options.subjectType] - The type of the sender, i.e. subject. */ - setupUntrustedCommunicationEip1193({ connectionStream, sender, subjectType }) { + setupUntrustedCommunicationEip1193({ + connectionStream, + sender, + subjectType, + }) { const { completedOnboarding } = this.onboardingController.store.getState(); const { usePhishDetect } = this.preferencesController.store.getState(); From 7d3d5f6a1baa4e81e5522cc3c08e81f93fe5dbfc Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 26 Jun 2024 12:13:35 -0700 Subject: [PATCH 33/37] Bifurcate streams --- app/scripts/metamask-controller.js | 99 +++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bf46ce339ca5..a6aa82917095 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4857,7 +4857,7 @@ export default class MetamaskController extends EventEmitter { const mux = setupMultiplex(connectionStream); // messages between inpage and background - this.setupProviderConnection( + this.setupProviderConnectionEip1193( mux.createStream('metamask-provider'), sender, inputSubjectType, @@ -4892,7 +4892,7 @@ export default class MetamaskController extends EventEmitter { const caipStream = createCaipStream(connectionStream); // messages between subject and background - this.setupProviderConnection(caipStream, sender, inputSubjectType); + this.setupProviderConnectionCaip(caipStream, sender, inputSubjectType); } /** @@ -4909,7 +4909,7 @@ export default class MetamaskController extends EventEmitter { const mux = setupMultiplex(connectionStream); // connect features this.setupControllerConnection(mux.createStream('controller')); - this.setupProviderConnection( + this.setupProviderConnectionEip1193( mux.createStream('provider'), sender, SubjectType.Internal, @@ -5022,7 +5022,7 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} sender - The sender of the messages on this stream * @param {SubjectType} subjectType - The type of the sender, i.e. subject. */ - setupProviderConnection(outStream, sender, subjectType) { + setupProviderConnectionEip1193(outStream, sender, subjectType) { let origin; if (subjectType === SubjectType.Internal) { origin = ORIGIN_METAMASK; @@ -5045,7 +5045,7 @@ export default class MetamaskController extends EventEmitter { tabId = sender.tab.id; } - const engine = this.setupProviderEngine({ + const engine = this.setupProviderEngineEip1193({ origin, sender, subjectType, @@ -5084,6 +5084,73 @@ export default class MetamaskController extends EventEmitter { } } + /** + * A method for serving our CAIP provider over a given stream. + * + * @param {*} outStream - The stream to provide over. + * @param {MessageSender | SnapSender} sender - The sender of the messages on this stream + * @param {SubjectType} subjectType - The type of the sender, i.e. subject. + */ + setupProviderConnectionCaip(outStream, sender, subjectType) { + let origin; + if (subjectType === SubjectType.Internal) { + origin = ORIGIN_METAMASK; + } else if (subjectType === SubjectType.Snap) { + origin = sender.snapId; + } else { + origin = new URL(sender.url).origin; + } + + if (sender.id && sender.id !== this.extension.runtime.id) { + this.subjectMetadataController.addSubjectMetadata({ + origin, + extensionId: sender.id, + subjectType: SubjectType.Extension, + }); + } + + let tabId; + if (sender.tab && sender.tab.id) { + tabId = sender.tab.id; + } + + const engine = this.setupProviderEngineCaip({ + origin, + tabId, + }); + + const dupeReqFilterStream = createDupeReqFilterStream(); + + // setup connection + const providerStream = createEngineStream({ engine }); + + const connectionId = this.addConnection(origin, { engine }); + + pipeline( + outStream, + dupeReqFilterStream, + providerStream, + outStream, + (err) => { + // handle any middleware cleanup + engine._middleware.forEach((mid) => { + if (mid.destroy && typeof mid.destroy === 'function') { + mid.destroy(); + } + }); + connectionId && this.removeConnection(origin, connectionId); + if (err) { + log.error(err); + } + }, + ); + + // Used to show wallet liveliness to the provider + if (subjectType !== SubjectType.Internal) { + this._notifyChainChangeForConnection({ engine }, origin); + } + } + /** * For snaps running in workers. * @@ -5099,7 +5166,7 @@ export default class MetamaskController extends EventEmitter { } /** - * A method for creating a provider that is safely restricted for the requesting subject. + * A method for creating an ethereum provider that is safely restricted for the requesting subject. * * @param {object} options - Provider engine options * @param {string} options.origin - The origin of the sender @@ -5107,7 +5174,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} options.subjectType - The type of the sender subject. * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab */ - setupProviderEngine({ origin, subjectType, sender, tabId }) { + setupProviderEngineEip1193({ origin, subjectType, sender, tabId }) { const engine = new JsonRpcEngine(); // Append origin to each request @@ -5498,6 +5565,24 @@ export default class MetamaskController extends EventEmitter { return engine; } + /** + * A method for creating a CAIP provider that is safely restricted for the requesting subject. + * + * @param {object} options - Provider engine options + * @param {string} options.origin - The origin of the sender + * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab + */ + setupProviderEngineCaip({ origin, tabId }) { + const engine = new JsonRpcEngine(); + + engine.push((request, _res, _next, end) => { + console.log('CAIP request received', { origin, tabId, request }); + return end('CAIP RPC Pipeline not yet implemented.'); + }); + + return engine; + } + /** * TODO:LegacyProvider: Delete * A method for providing our public config info over a stream. From 3c8b8328e714f86313fe23f8e465373529ff134c Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 26 Jun 2024 12:28:53 -0700 Subject: [PATCH 34/37] fix specs --- app/scripts/metamask-controller.test.js | 115 ------------------------ 1 file changed, 115 deletions(-) diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index e78e09b4ce18..f7b99be06d32 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -1399,121 +1399,6 @@ describe('MetaMaskController', () => { }); describe('#setupUntrustedCommunicationCaip', () => { - const mockTxParams = { from: TEST_ADDRESS }; - - beforeEach(() => { - initializeMockMiddlewareLog(); - metamaskController.preferencesController.setSecurityAlertsEnabled( - false, - ); - jest - .spyOn(metamaskController.onboardingController.store, 'getState') - .mockReturnValue({ completedOnboarding: true }); - metamaskController.preferencesController.setUsePhishDetect(true); - }); - - afterAll(() => { - tearDownMockMiddlewareLog(); - }); - - it('adds a tabId, origin and networkClient to requests', async () => { - const messageSender = { - url: 'http://mycrypto.com', - tab: { id: 456 }, - }; - const streamTest = createThroughStream((chunk, _, cb) => { - if (chunk.data && chunk.data.method) { - cb(null, chunk); - return; - } - cb(); - }); - - metamaskController.setupUntrustedCommunicationCaip({ - connectionStream: streamTest, - sender: messageSender, - }); - - const message = { - id: 1999133338649204, - jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', - }; - await new Promise((resolve) => { - streamTest.write( - { - type: 'caip-x', - data: message, - }, - null, - () => { - setTimeout(() => { - expect(loggerMiddlewareMock.requests[0]).toHaveProperty( - 'origin', - 'http://mycrypto.com', - ); - expect(loggerMiddlewareMock.requests[0]).toHaveProperty( - 'tabId', - 456, - ); - expect(loggerMiddlewareMock.requests[0]).toHaveProperty( - 'networkClientId', - 'networkConfigurationId1', - ); - resolve(); - }); - }, - ); - }); - }); - - it('should add only origin to request if tabId not provided', async () => { - const messageSender = { - url: 'http://mycrypto.com', - }; - const streamTest = createThroughStream((chunk, _, cb) => { - if (chunk.data && chunk.data.method) { - cb(null, chunk); - return; - } - cb(); - }); - - metamaskController.setupUntrustedCommunicationCaip({ - connectionStream: streamTest, - sender: messageSender, - }); - - const message = { - id: 1999133338649204, - jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', - }; - await new Promise((resolve) => { - streamTest.write( - { - type: 'caip-x', - data: message, - }, - null, - () => { - setTimeout(() => { - expect(loggerMiddlewareMock.requests[0]).not.toHaveProperty( - 'tabId', - ); - expect(loggerMiddlewareMock.requests[0]).toHaveProperty( - 'origin', - 'http://mycrypto.com', - ); - resolve(); - }); - }, - ); - }); - }); - it.todo('should only process `caip-x` CAIP formatted messages'); }); From 439f27ed28a7c64be5b31648c6316ca89e72b786 Mon Sep 17 00:00:00 2001 From: jiexi Date: Wed, 26 Jun 2024 14:07:55 -0700 Subject: [PATCH 35/37] Update app/scripts/metamask-controller.js Co-authored-by: Shane --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a6aa82917095..9f892ebf8429 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -5577,7 +5577,7 @@ export default class MetamaskController extends EventEmitter { engine.push((request, _res, _next, end) => { console.log('CAIP request received', { origin, tabId, request }); - return end('CAIP RPC Pipeline not yet implemented.'); + return end(new Error('CAIP RPC Pipeline not yet implemented.')); }); return engine; From 9f352030a349cbf7d420fe2037d94c1f27ff03fe Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 26 Jun 2024 14:39:00 -0700 Subject: [PATCH 36/37] remove requestAccountTabIds for CAIP --- app/scripts/background.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 313a47736953..6d6fd0c083c5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -780,17 +780,6 @@ export function setupController( const { origin } = url; trackDappView(remotePort); - - // TODO: remove this when we separate the legacy and multichain rpc pipelines - remotePort.onMessage.addListener((msg) => { - if ( - msg.type === 'caip-x' && - msg.data && - msg.data.method === MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS - ) { - requestAccountTabIds[origin] = tabId; - } - }); } const portStream = From c8ce2f81a972e2ae20d0dd2c9cda069a4978e4b4 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 26 Jun 2024 16:19:32 -0700 Subject: [PATCH 37/37] lint --- app/scripts/background.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 6d6fd0c083c5..63ac039ae83a 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -775,10 +775,6 @@ export function setupController( // this is triggered when a new tab is opened, or origin(url) is changed if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { - const tabId = remotePort.sender.tab.id; - const url = new URL(remotePort.sender.url); - const { origin } = url; - trackDappView(remotePort); }