Guide to Porting MetaMask to a New Environment

https://github.com/MetaMask/metamask-extension/blob/develop/docs/porting_to_new_environment.mdjavascript

 

MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some useful abstractions that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily (although it still could be easier, and please let us know if you get stuck!)html

MetaMask已經持續開發了近兩年,咱們逐漸發現了一些有用的抽象概念,使咱們可以更容易地進行開發。其中的兩個層使metamask請求可以愈來愈容易地移植到新的環境和上下文java

Before we get started, it's worth becoming familiar with our basic architecture:node

在咱們開始以前,有必要熟悉一下咱們的基本架構:react

在本地測試一下,經過下面UI code,調用web3:linux

<script type="text/javascript">
        //這些註釋的地方都是以前
    
        window.addEventListener('load', function() {
            console.log(window.web3);  //調用web3
        });
</script>

獲得結果:git

The metamask-background describes the file at app/scripts/background.js, which is the web extension singleton. This context instantiates an instance of the MetaMask Controller, which represents the user's accounts, a connection to the blockchain, and the interaction with new Dapps.github

metamask-background描述了爲web擴展單例的文件app/scripts/background.js(代碼的講解在本博客metamask源碼學習-background.js)。該上下文實例化了一個MetaMask控制器的實例,該實例表示用戶的賬戶、到區塊鏈的鏈接以及與新Dapps的交互。web

When a new site is visited, the WebExtension creates a new ContentScript in that page's context, which can be seen at app/scripts/contentscript.js. This script represents a per-page setup process, which creates the per-page web3api, connects it to the background script via the Port API (wrapped in a stream abstraction), and injected into the DOM before anything loads.chrome

當訪問一個新站點時,WebExtension會在該頁面的上下文中建立一個新的ContentScript,能夠在app/scripts/ ContentScript .js中看到其的代碼(本博客中metamask源碼學習-contentscript.js)。這個腳本表示每一個頁面的設置過程,它建立每一個頁面的web3api,經過端口API(封裝在流抽象中)將其鏈接到後臺腳本,並在加載以前注入DOM。

The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the InpageProvider in the inpage.js script, you will be able to understand how the port-stream is just a thin wrapper around the postMessage API, and a similar stream API can be wrapped around any communication channel to communicate with the MetaMaskController via its setupUntrustedCommunication(stream, domain) method.

將MetaMask移植到新平臺上最使人困惑的部分是咱們經過上下文之間的一系列流提供Web3 API的方式。當你瞭解了咱們如何在inpage.js腳本中建立InpageProvider以後,您將可以理解端口流爲什麼只是一個圍繞postMessage API的thin包裝器以及爲什麼一個相似的流API能夠被包裝在任何通訊通道上,經過它的setupUntrustedCommunication(流,域)方法與MetaMaskController進行通訊。(代碼詳細信息看本博客metamask源碼學習-inpage.js

 

The MetaMask Controller(詳細信息看本博客metamask源碼學習-metamask-controller.js)

The core functionality of MetaMask all lives in what we call The MetaMask Controller. Our goal for this file is for it to eventually be its own javascript module that can be imported into any JS-compatible context, allowing it to fully manage an app's relationship to Ethereum.

MetaMask的核心功能都存在於咱們所稱的The MetaMask控制器中。咱們對這個文件的目標是讓它最終成爲可以被導入到任何兼容於js的上下文中的屬於本身的javascript模塊,,從而徹底管理應用程序與以太坊之間的關係。

Constructor

When calling new MetaMask(opts), many platform-specific options are configured. The keys on opts are as follows:

在調用new MetaMask(opts)時,配置了許多特定於平臺的選項。以下:

  • initState: The last emitted state, used for restoring persistent state between sessions.最後發出的狀態,用於在會話之間恢復持久狀態
  • platform: The platform object defines a variety of platform-specific functions, including opening the confirmation view, and opening web sites.平臺對象定義了各類特定於平臺的功能,包括打開確認視圖和打開web站點。
  • encryptor - An object that provides access to the desired encryption methods.提供對所需加密方法的訪問的對象
Encryptor

An object that provides two simple methods, which can encrypt in any format you prefer. This parameter is optional, and will default to the browser-native WebCrypto API.

提供兩種能夠以你喜歡的任何格式進行加密的簡單方法的對象。這個參數是可選的,默認爲瀏覽器本地的WebCrypto API。

  • encrypt(password, object) - returns a Promise of a string that is ready for storage.返回一個可用於存儲的字符串的Promise
  • decrypt(password, encryptedString) - Accepts the encrypted output of encrypt and returns a Promise of a restored object as it was encrypted.接受加密的輸出encryptedString,並返回解密的對象的promise。
Platform Options

The platform object has a variety of options:

  • reload (function) - Will be called when MetaMask would like to reload its own context.將在MetaMask想要從新加載其本身的上下文時調用
  • openWindow ({ url }) - Will be called when MetaMask would like to open a web page. It will be passed a single options object with a url key, with a string value.將在MetaMask想要打開web頁面時調用。它將傳遞一個帶有url鍵和字符串值的單一選項對象
  • getVersion() - Should return the current MetaMask version, as described in the current CHANGELOG.md or app/manifest.json.應該返回當前MetaMask的版本,即在當前的 CHANGELOG.mdapp/manifest.json中所述的版本號

metamask.getState()

This method returns a javascript object representing the current MetaMask state. This includes things like known accounts, sent transactions, current exchange rates, and more! The controller is also an event emitter, so you can subscribe to state updates via metamask.on('update', handleStateUpdate). State examples available here under the metamask key. (Warning: some are outdated)

metamask-extension/app/scripts/metamask-controller.js

//=============================================================================
// EXPOSED TO THE UI SUBSYSTEM
//=============================================================================

  /**
   * The metamask-state of the various controllers, made available to the UI
   *
   * @returns {Object} status
   */
  getState () {
    const wallet = this.configManager.getWallet()
    const vault = this.keyringController.store.getState().vault
    const isInitialized = (!!wallet || !!vault)

    return {
      ...{ isInitialized },
      ...this.memStore.getFlatState(),
      ...this.configManager.getConfig(),
      ...{
        lostAccounts: this.configManager.getLostAccounts(),
        seedWords: this.configManager.getSeedWords(),
        forgottenPassword: this.configManager.getPasswordForgotten(),
      },
    }
  }

該方法返回一個javascript對象,表示當前的MetaMask狀態。這包括已知的賬戶、發送的事務、當前匯率等等。控制器也是一個事件發射器,因此你能夠經過metamask.on('update', handleStateUpdate)來訂閱狀態更新。狀態示例即一系列json文件,即"metamask"鍵下的值的信息(警告:有些已通過時)

metamask.getApi()

Returns a JavaScript object filled with callback functions representing every operation our user interface ever performs. Everything from creating new accounts, changing the current network, to sending a transaction, is provided via these API methods. We export this external API on an object because it allows us to easily expose this API over a port using dnode, which is how our WebExtension's UI works!

返回一個JavaScript對象,該對象中包含回調函數,表示用戶界面執行的每一個操做。經過這些API方法提供了從建立新賬戶、更改當前網絡到發送事務的全部內容。咱們在對象上導出這個外部API,由於它容許咱們使用dnode在端口上輕鬆地公開這個API,這就是WebExtension UI的工做方式!

metamask-extension/app/scripts/metamask-controller.js

  /**
   * Returns an Object containing API Callback Functions.
   * These functions are the interface for the UI.
   * The API object can be transmitted over a stream with dnode.
   *
   * @returns {Object} Object containing API functions.
   */
  getApi () {
    const keyringController = this.keyringController
    const preferencesController = this.preferencesController
    const txController = this.txController
    const noticeController = this.noticeController
    const addressBookController = this.addressBookController
    const networkController = this.networkController

    return {
      // etc
      getState: (cb) => cb(null, this.getState()),
      setCurrentCurrency: this.setCurrentCurrency.bind(this),
      setUseBlockie: this.setUseBlockie.bind(this),
      setCurrentLocale: this.setCurrentLocale.bind(this),
      markAccountsFound: this.markAccountsFound.bind(this),
      markPasswordForgotten: this.markPasswordForgotten.bind(this),
      unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
      getGasPrice: (cb) => cb(null, this.getGasPrice()),

      // coinbase
      buyEth: this.buyEth.bind(this),
      // shapeshift
      createShapeShiftTx: this.createShapeShiftTx.bind(this),

      // primary HD keyring management
      addNewAccount: nodeify(this.addNewAccount, this),
      placeSeedWords: this.placeSeedWords.bind(this),
      verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
      clearSeedWordCache: this.clearSeedWordCache.bind(this),
      resetAccount: nodeify(this.resetAccount, this),
      removeAccount: nodeify(this.removeAccount, this),
      importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),

      // hardware wallets
      connectHardware: nodeify(this.connectHardware, this),
      forgetDevice: nodeify(this.forgetDevice, this),
      checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
      unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this),

      // vault management
      submitPassword: nodeify(this.submitPassword, this),

      // network management
      setProviderType: nodeify(networkController.setProviderType, networkController),
      setCustomRpc: nodeify(this.setCustomRpc, this),

      // PreferencesController
      setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
      addToken: nodeify(preferencesController.addToken, preferencesController),
      removeToken: nodeify(preferencesController.removeToken, preferencesController),
      setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
      setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
      setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),

      // AddressController
      setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),

      // KeyringController
      setLocked: nodeify(keyringController.setLocked, keyringController),
      createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
      createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
      addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
      exportAccount: nodeify(keyringController.exportAccount, keyringController),

      // txController
      cancelTransaction: nodeify(txController.cancelTransaction, txController),
      updateTransaction: nodeify(txController.updateTransaction, txController),
      updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
      retryTransaction: nodeify(this.retryTransaction, this),
      getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
      isNonceTaken: nodeify(txController.isNonceTaken, txController),
      estimateGas: nodeify(this.estimateGas, this),

      // messageManager
      signMessage: nodeify(this.signMessage, this),
      cancelMessage: this.cancelMessage.bind(this),

      // personalMessageManager
      signPersonalMessage: nodeify(this.signPersonalMessage, this),
      cancelPersonalMessage: this.cancelPersonalMessage.bind(this),

      // personalMessageManager
      signTypedMessage: nodeify(this.signTypedMessage, this),
      cancelTypedMessage: this.cancelTypedMessage.bind(this),

      // notices
      checkNotices: noticeController.updateNoticesList.bind(noticeController),
      markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
    }
  }

 

The UI(詳細信息看本博客metamask源碼學習-ui/index.js)

The MetaMask UI is essentially just a website that can be configured by passing it the API and state subscriptions from above. Anyone could make a UI that consumes these, effectively reskinning MetaMask.

MetaMask UI本質上只是一個網站,能夠經過從上面傳遞API和狀態訂閱來配置它。任何人均可以建立一個使用這些的UI,有效地從新設計MetaMask

You can see this in action in our file ui/index.js. There you can see an argument being passed in named accountManager, which is essentially a MetaMask controller (forgive its really outdated parameter name!). With access to that object, the UI is able to initialize a whole React/Redux app that relies on this API for its account/blockchain-related/persistent states.

你能夠從文件ui/index.js中看見它的實現。在這裏,您能夠看到一個參數在accountManager中傳遞,accountManager本質上是一個MetaMask控制器(該參數名稱已通過時!)經過對該對象的訪問,UI可以初始化整個依賴於該API實現其賬戶/區塊鏈相關/持久狀態的React/Redux應用程序。

metamask-extension/ui/index.js

const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./app/root')
const actions = require('./app/actions')
const configureStore = require('./app/store')
const txHelper = require('./lib/tx-helper')
const { fetchLocale } = require('./i18n-helper')
const log = require('loglevel')

module.exports = launchMetamaskUi

log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')

function launchMetamaskUi (opts, cb) {
  var accountManager = opts.accountManager//accountManager就是一個metamask控制器,因此metamask-controller.js中的函數其都能調用,UI通常就調用getApi ()getState()兩個函數
  actions._setBackgroundConnection(accountManager)//設置後臺的鏈接信息
  // check if we are unlocked first
  accountManager.getState(function (err, metamaskState) {//返回一個javascript對象,表示當前的MetaMask狀態
    if (err) return cb(err)
    startApp(metamaskState, accountManager, opts) 
      .then((store) => {
        cb(null, store)
      })
  })
}

//打開APP
async function startApp (metamaskState, accountManager, opts) { // parse opts if (!metamaskState.featureFlags) metamaskState.featureFlags = {} const currentLocaleMessages = metamaskState.currentLocale ? await fetchLocale(metamaskState.currentLocale)//獲得`./_locales/${metamaskState.currentLocale}/messages.json`文件 : {} const enLocaleMessages = await fetchLocale('en') const store = configureStore({//配置metamask環境信息,如中間件等 // metamaskState represents the cross-tab state metamask: metamaskState, // appState represents the current tab's popup state appState: {}, localeMessages: { current: currentLocaleMessages, en: enLocaleMessages, }, // Which blockchain we are using: networkVersion: opts.networkVersion, }) // if unconfirmed txs, start on txConf page
//獲得時間由小到大排序的全部信息 const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network) const numberOfUnapprivedTx = unapprovedTxsAll.length if (numberOfUnapprivedTx > 0) { store.dispatch(actions.showConfTxPage({ id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,//配置actions中showConfTxPage的值 })) } accountManager.on('update', function (metamaskState) {//若是metamaskState由更新時觸發 store.dispatch(actions.updateMetamaskState(metamaskState)) }) // global metamask api - used by tooling global.metamask = { updateCurrentLocale: (code) => { store.dispatch(actions.updateCurrentLocale(code)) }, setProviderType: (type) => { store.dispatch(actions.setProviderType(type)) }, } // start app,使用react框架來啓動該metamask app render( h(Root, { // inject initial state store: store, } ), opts.container) return store }

 

Putting it Together

As an example, a WebExtension is always defined by a manifest.json file. 

metamask-extension/app/manifest.json

{
  "name": "__MSG_appName__",
  "short_name": "__MSG_appName__",
  "version": "4.11.1",
  "manifest_version": 2,
  "author": "https://metamask.io",
  "description": "__MSG_appDescription__",
  "commands": {
    "_execute_browser_action": {
      "suggested_key": {
        "windows": "Alt+Shift+M",
        "mac": "Alt+Shift+M",
        "chromeos": "Alt+Shift+M",
        "linux": "Alt+Shift+M"
      }
    }
  },
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "applications": {
    "gecko": {
      "id": "webextension@metamask.io"
    }
  },
  "default_locale": "en",
  "background": {
    "scripts": [
      "chromereload.js",
      "background.js"
    ],
    "persistent": true
  },
  "browser_action": {
    "default_icon": {
      "19": "images/icon-19.png",
      "38": "images/icon-38.png"
    },
    "default_title": "MetaMask",
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": [
        "file://*/*",
        "http://*/*",
        "https://*/*"
      ],
      "js": [
        "contentscript.js"
      ],
      "run_at": "document_start",
      "all_frames": true
    }
  ],
  "permissions": [
    "storage",
    "unlimitedStorage",
    "clipboardWrite",
    "http://localhost:8545/",
    "https://*.infura.io/",
    "activeTab",
    "webRequest",
    "*://*.eth/",
    "*://*.test/",
    "notifications"
  ],
  "web_accessible_resources": [
    "inpage.js",
    "phishing.html"
  ],
  "externally_connectable": {
    "matches": [
      "https://metamask.io/*"
    ],
    "ids": [
      "*"
    ]
  }
}

 

In ours, you can see that background.js is defined as a script to run in the background(上面代碼藍色標識處), and this is the file that we use to initialize the MetaMask controller.

在上面的代碼中,能夠看見 background.js被定義成在後臺運行的腳本而且它(即 background.js其代碼的講解在本博客metamask源碼學習-background.js)也是咱們用來初始化MetaMask控制器的文件

In that file, there's a lot going on, so it's maybe worth focusing on our MetaMask controller constructor to start. It looks something like this:

const controller = new MetamaskController({
    // User confirmation callbacks:
    showUnconfirmedMessage: triggerUi,
    unlockAccountMessage: triggerUi,
    showUnapprovedTx: triggerUi,
    // initial state
    initState,
    // platform specific api
    platform,
})

Since background.js is essentially the Extension setup file, we can see it doing all the things specific to the extension platform:

由於background.js本質上是擴展設置文件,咱們能夠看到它作全部特定於擴展平臺的事情

  • Defining how to open the UI for new messages, transactions, and even requests to unlock (reveal to the site) their account.定義如何爲新消息、事務、甚至爲解鎖(向站點顯示)其賬戶的請求打開UI
  • Provide the instance's initial state, leaving MetaMask persistence to the platform.提供實例的初始狀態,將MetaMask持久化保留到平臺
  • Providing a platform object. This is becoming our catch-all adapter for platforms to define a few other platform-variant features we require, like opening a web link. (Soon we will be moving encryption out here too, since our browser-encryption isn't portable enough!)提供平臺對象。這將成爲平臺的通用適配器,用於定義咱們須要的其餘一些平臺變體特性,好比打開web連接。(不久咱們也將把加密技術移到這裏,由於咱們的瀏覽器加密還不夠便攜!)

Ports, streams, and Web3!

Everything so far has been enough to create a MetaMask wallet on virtually any platform that runs JS, but MetaMask's most unique feature isn't being a wallet, it's providing an Ethereum-enabled JavaScript context to websites.

到目前爲止,在幾乎全部運行JS的平臺上均可以建立一個MetaMask wallet,但MetaMask最獨特的功能不是錢包,而是爲網站提供了一個Ethereum-enabled的JavaScript環境。

MetaMask has two kinds of duplex stream APIs that it exposes:    MetaMask有兩種公開的雙向流api

  • metamask.setupTrustedCommunication(connectionStream, originDomain) - This stream is used to connect the user interface over a remote port, and may not be necessary for contexts where the interface and the metamask-controller share a process.此流用於經過遠程端口鏈接用戶界面,對於接口和metamask控制器共享進程的上下文可能不須要此流

都在metamask-extension/app/scripts/metamask-controller.js

  /**
   * Used to create a multiplexed stream for connecting to a trusted context,
   * like our own user interfaces, which have the provider APIs, but also
   * receive the exported API from this controller, which includes trusted
   * functions, like the ability to approve transactions or sign messages.
   *
   * @param {*} connectionStream - The duplex stream to connect to.
   * @param {string} originDomain - The domain requesting the connection,
   * used in logging and error reporting.
   */
  setupTrustedCommunication (connectionStream, originDomain) {
    // setup multiplexing
    const mux = setupMultiplex(connectionStream)
    // connect features
    this.setupControllerConnection(mux.createStream('controller'))
    this.setupProviderConnection(mux.createStream('provider'), originDomain)
  }

 

  • metamask.setupUntrustedCommunication(connectionStream, originDomain) - This method is used to connect a new web site's web3 API to MetaMask's blockchain connection. Additionally, the originDomain is used to block detected phishing sites.此方法用於將新web站點的web3 API鏈接到MetaMask的區塊鏈鏈接。另外,originDomain能夠用來阻止檢測到的釣魚網站
//=============================================================================
// SETUP
//=============================================================================

  /**
   * Used to create a multiplexed stream for connecting to an untrusted context
   * like a Dapp or other extension.
   * @param {*} connectionStream - The Duplex stream to connect to.
   * @param {string} originDomain - The domain requesting the stream, which
   * may trigger a blacklist reload.
   */
  setupUntrustedCommunication (connectionStream, originDomain) {
    // Check if new connection is blacklisted
    if (this.blacklistController.checkForPhishing(originDomain)) {
      log.debug('MetaMask - sending phishing warning for', originDomain)
      this.sendPhishingWarning(connectionStream, originDomain)
      return
    }

    // setup multiplexing
    const mux = setupMultiplex(connectionStream)
    // connect features
    this.setupProviderConnection(mux.createStream('provider'), originDomain)
    this.setupPublicConfig(mux.createStream('publicConfig'))
  }

上面有調用到的函數:

  /**
   * Called when we detect a suspicious domain. Requests the browser redirects
   * to our anti-phishing page.
   *
   * @private
   * @param {*} connectionStream - The duplex stream to the per-page script,
   * for sending the reload attempt to.
   * @param {string} hostname - The URL that triggered the suspicion.
   */
  sendPhishingWarning (connectionStream, hostname) {
    const mux = setupMultiplex(connectionStream)
    const phishingStream = mux.createStream('phishing')
    phishingStream.write({ hostname })
  }

  /**
   * A method for providing our API over a stream using Dnode.
   * @param {*} outStream - The stream to provide our API over.
   */
  setupControllerConnection (outStream) {
    const api = this.getApi()
    const dnode = Dnode(api)
    pump(
      outStream,
      dnode,
      outStream,
      (err) => {
        if (err) log.error(err)
      }
    )
    dnode.on('remote', (remote) => {
      // push updates to popup
      const sendUpdate = remote.sendUpdate.bind(remote)
      this.on('update', sendUpdate)
    })
  }

  /**
   * A method for serving our ethereum provider over a given stream.
   * @param {*} outStream - The stream to provide over.
   * @param {string} origin - The URI of the requesting resource.
   */
  setupProviderConnection (outStream, origin) {
    // setup json rpc engine stack
    const engine = new RpcEngine()

    // create filter polyfill middleware
    const filterMiddleware = createFilterMiddleware({
      provider: this.provider,
      blockTracker: this.provider._blockTracker,
    })

    engine.push(createOriginMiddleware({ origin }))
    engine.push(createLoggerMiddleware({ origin }))
    engine.push(filterMiddleware)
    engine.push(createProviderMiddleware({ provider: this.provider }))

    // setup connection
    const providerStream = createEngineStream({ engine })
    pump(
      outStream,
      providerStream,
      outStream,
      (err) => {
        // cleanup filter polyfill middleware
        filterMiddleware.destroy()
        if (err) log.error(err)
      }
    )
  }

  /**
   * A method for providing our public config info over a stream.
   * This includes info we like to be synchronous if possible, like
   * the current selected account, and network ID.
   *
   * Since synchronous methods have been deprecated in web3,
   * this is a good candidate for deprecation.
   *
   * @param {*} outStream - The stream to provide public config over.
   */
  setupPublicConfig (outStream) {
    pump(
      asStream(this.publicConfigStore),
      outStream,
      (err) => {
        if (err) log.error(err)
      }
    )
  }

 

Web3 as a Stream

If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).

若是您正在爲一個新平臺開發一個基於MetaMask-powered的瀏覽器,那麼最棘手的任務之一就是將Web3 API注入到訪問的網站中。在WebExtensions,咱們實際上須要經過總共三個JS上下文來傳輸數據,只是爲了讓站點與咱們的後臺進程對話(site -> contentscript ->後臺)。

To see how we do that, you can refer to the inpage script that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the inpage-provider, which you can see stubs a few methods out, but mostly just passes calls to sendAsync through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.

要了解咱們如何作到這一點,您能夠參考咱們注入到每一個網站的inpage腳本。在那裏,您能夠看到它建立了一個到後臺的多路複用流,並使用它來初始化inpage-provider,你能夠看到inpage-provider的一些方法輸出了stubs(存根),但它的大多數方法只是經過它傳遞的流來傳遞對sendAsync的調用!一旦你有了一個可用的MetaMask流,以上就是在遠程上下文中建立相似web3的API所須要的全部操做。

In inpage.js you can see we create a PortStream, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!

inpage.js你能夠看到咱們建立了一個咱們用來將WebExtension端口包裝成流的PortStream類,所以咱們能夠在WebExtension的更加不規則的API表面上重用咱們喜歡的流抽象。在新的平臺中,您可能須要以不一樣的方式構造這個流。關鍵是您須要構建一個從站點上下文到後臺的流。一旦你設置好了,它就會像魔術同樣運做!

If streams seem new and confusing to you, that's ok, they can seem strange at first. To help learn them, we highly recommend reading Substack's Stream Handbook, or going through NodeSchool's interactive command-line class Stream Adventure, also maintained by Substack.

若是你對流並非很瞭解,那就到Substack's Stream Handbook處去學習吧

還有中文版:https://github.com/jabez128/stream-handbook

相關文章
相關標籤/搜索