aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/controllers/currency.js
blob: cc78ef8a6f2fbfc169933bacd915f5ff6a8d848a (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
const ObservableStore = require('obs-store')
const extend = require('xtend')
const log = require('loglevel')

// every ten minutes
const POLLING_INTERVAL = 600000

class CurrencyController {

  /**
   * Controller responsible for managing data associated with the currently selected currency.
   *
   * @typedef {Object} CurrencyController
   * @param {object} opts Overrides the defaults for the initial state of this.store
   * @property {array} opts.initState  initializes the the state of the CurrencyController. Can contain an
   * currentCurrency, conversionRate and conversionDate properties
   * @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently
   * selected by the user
   * @property {number} conversionRate The conversion rate from ETH to the selected currency.
   * @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds
   * since midnight of January 1, 1970
   * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
   * Used to clear an existing interval on subsequent calls of that method.
   * @property {string} nativeCurrency The ticker/symbol of the native chain currency
   *
   */
  constructor (opts = {}) {
    const initState = extend({
      currentCurrency: 'usd',
      conversionRate: 0,
      conversionDate: 'N/A',
      nativeCurrency: 'ETH',
    }, opts.initState)
    this.store = new ObservableStore(initState)
  }

  //
  // PUBLIC METHODS
  //

  /**
   * A getter for the nativeCurrency property
   *
   * @returns {string} A 2-4 character shorthand that describes the specific currency
   *
   */
  getNativeCurrency () {
    return this.store.getState().nativeCurrency
  }

  /**
   * A setter for the nativeCurrency property
   *
   * @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store
   *
   */
  setNativeCurrency (nativeCurrency) {
    this.store.updateState({
      nativeCurrency,
      ticker: nativeCurrency,
    })
  }

  /**
   * A getter for the currentCurrency property
   *
   * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user
   *
   */
  getCurrentCurrency () {
    return this.store.getState().currentCurrency
  }

  /**
   * A setter for the currentCurrency property
   *
   * @param {string} currentCurrency The new currency to set as the currentCurrency in the store
   *
   */
  setCurrentCurrency (currentCurrency) {
    this.store.updateState({ currentCurrency })
  }

  /**
   * A getter for the conversionRate property
   *
   * @returns {string} The conversion rate from ETH to the selected currency.
   *
   */
  getConversionRate () {
    return this.store.getState().conversionRate
  }

  /**
   * A setter for the conversionRate property
   *
   * @param {number} conversionRate The new rate to set as the conversionRate in the store
   *
   */
  setConversionRate (conversionRate) {
    this.store.updateState({ conversionRate })
  }

  /**
   * A getter for the conversionDate property
   *
   * @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of
   * January 1, 1970
   *
   */
  getConversionDate () {
    return this.store.getState().conversionDate
  }

  /**
   * A setter for the conversionDate property
   *
   * @param {number} conversionDate The date, expressed in milliseconds since midnight of January 1, 1970, that the
   * conversionRate was set
   *
   */
  setConversionDate (conversionDate) {
    this.store.updateState({ conversionDate })
  }

  /**
   * Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is
   * fetched from an external API
   *
   */
  async updateConversionRate () {
    let currentCurrency, nativeCurrency
    try {
      currentCurrency = this.getCurrentCurrency()
      nativeCurrency = this.getNativeCurrency()
      // select api
      let apiUrl
      if (nativeCurrency === 'ETH') {
        // ETH
        apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`
      } else {
        // ETC
        apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
      }
      // attempt request
      let response
      try {
        response = await fetch(apiUrl)
      } catch (err) {
        log.error(new Error(`CurrencyController - Failed to request currency from Infura:\n${err.stack}`))
        return
      }
      // parse response
      let rawResponse
      let parsedResponse
      try {
        rawResponse = await response.text()
        parsedResponse = JSON.parse(rawResponse)
      } catch (err) {
        log.error(new Error(`CurrencyController - Failed to parse response "${rawResponse}"`))
        return
      }
      // set exhcange rate
      if (nativeCurrency === 'ETH') {
        // ETH
        this.setConversionRate(Number(parsedResponse.bid))
        this.setConversionDate(Number(parsedResponse.timestamp))
      } else {
        // ETC
        if (parsedResponse[currentCurrency.toUpperCase()]) {
          this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()]))
          this.setConversionDate(parseInt((new Date()).getTime() / 1000))
        } else {
          this.setConversionRate(0)
          this.setConversionDate('N/A')
        }
      }
    } catch (err) {
      // reset current conversion rate
      this.setConversionRate(0)
      this.setConversionDate('N/A')
      // throw error
      log.error(new Error(`CurrencyController - Failed to query rate for currency "${currentCurrency}":\n${err.stack}`))
      return
    }
  }

  /**
   * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
   * stored at the controller's conversionInterval property. If it is called and such an id already exists, the
   * previous interval is clear and a new one is created.
   *
   */
  scheduleConversionInterval () {
    if (this.conversionInterval) {
      clearInterval(this.conversionInterval)
    }
    this.conversionInterval = setInterval(() => {
      this.updateConversionRate()
    }, POLLING_INTERVAL)
  }
}

module.exports = CurrencyController