aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib/inpage-provider.js
blob: f1ba2e0ed0ab7eb7e17eec4b27ae68aeddfe20a7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const Streams = require('mississippi')
const StreamProvider = require('web3-stream-provider')
const ObjectMultiplex = require('./obj-multiplex')
const RemoteStore = require('./remote-store.js').RemoteStore
const createRandomId = require('./random-id')

module.exports = MetamaskInpageProvider

function MetamaskInpageProvider (connectionStream) {
  const self = this

  // setup connectionStream multiplexing
  var multiStream = ObjectMultiplex()
  Streams.pipe(connectionStream, multiStream, connectionStream, function (err) {
    let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask'
    if (err) warningMsg += '\n' + err.stack
    console.warn(warningMsg)
  })
  self.multiStream = multiStream

  // subscribe to metamask public config
  var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config')
  var storeStream = publicConfigStore.createStream()
  Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) {
    let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask publicConfig'
    if (err) warningMsg += '\n' + err.stack
    console.warn(warningMsg)
  })
  self.publicConfigStore = publicConfigStore

  // connect to async provider
  var asyncProvider = new StreamProvider()
  Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) {
    let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask provider'
    if (err) warningMsg += '\n' + err.stack
    console.warn(warningMsg)
  })
  asyncProvider.on('error', console.error.bind(console))
  self.asyncProvider = asyncProvider

  self.idMap = {}
  // handle sendAsync requests via asyncProvider
  self.sendAsync = function(payload, cb){
    // rewrite request ids
    var request = eachJsonMessage(payload, (message) => {
      var newId = createRandomId()
      self.idMap[newId] = message.id
      message.id = newId
      return message
    })
    // forward to asyncProvider
    asyncProvider.sendAsync(request, function(err, res){
      if (err) return cb(err)
      // transform messages to original ids
      eachJsonMessage(res, (message) => {
        var oldId = self.idMap[message.id]
        delete self.idMap[message.id]
        message.id = oldId
        return message
      })
      cb(null, res)
    })
  }
}

MetamaskInpageProvider.prototype.send = function (payload) {
  const self = this

  let selectedAddress
  let result = null
  switch (payload.method) {

    case 'eth_accounts':
      // read from localStorage
      selectedAddress = self.publicConfigStore.get('selectedAddress')
      result = selectedAddress ? [selectedAddress] : []
      break

    case 'eth_coinbase':
      // read from localStorage
      selectedAddress = self.publicConfigStore.get('selectedAddress')
      result = selectedAddress || '0x0000000000000000000000000000000000000000'
      break

    // throw not-supported Error
    default:
      var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client'
      var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.`
      throw new Error(message)

  }

  // return the result
  return {
    id: payload.id,
    jsonrpc: payload.jsonrpc,
    result: result,
  }
}

MetamaskInpageProvider.prototype.sendAsync = function () {
  throw new Error('MetamaskInpageProvider - sendAsync not overwritten')
}

MetamaskInpageProvider.prototype.isConnected = function () {
  return true
}

// util

function remoteStoreWithLocalStorageCache (storageKey) {
  // read local cache
  var initState = JSON.parse(localStorage[storageKey] || '{}')
  var store = new RemoteStore(initState)
  // cache the latest state locally
  store.subscribe(function (state) {
    localStorage[storageKey] = JSON.stringify(state)
  })

  return store
}

function eachJsonMessage(payload, transformFn){
  if (Array.isArray(payload)) {
    return payload.map(transformFn)
  } else {
    return transformFn(payload)
  }
}