ethereum/mist

View on GitHub
modules/preloader/include/mistAPI.js

Summary

Maintainability
F
3 days
Test Coverage
/**
@module MistAPI
*/

const _ = require('underscore');
const { ipcRenderer } = require('electron');
const packageJson = require('./../../../package.json');

module.exports = () => {
  let queue = [];
  const prefix = 'entry_';
  const MIST_SUBMENU_LIMIT = 100;

  // todo: error handling
  const filterAdd = options => {
    if (!(options instanceof Object)) {
      return false;
    }

    return ['name'].every(e => e in options);
  };

  // filterId the id to only contain a-z A-Z 0-9
  const filterId = str => {
    const filteredStr = String(str);
    let newStr = '';
    if (filteredStr) {
      for (let i = 0; i < filteredStr.length; i += 1) {
        if (/[a-zA-Z0-9_-]/.test(filteredStr.charAt(i))) {
          newStr += filteredStr.charAt(i);
        }
      }
    }
    return newStr;
  };

  /**
    Mist API

    Provides an API for all dapps, which specifically targets features from the Mist browser

    @class mist
    @constructor
    */
  const mist = {
    callbacks: {},
    version: packageJson.version,
    license: packageJson.license,
    platform: process.platform,
    requestAccount(callback) {
      if (callback) {
        if (!this.callbacks.connectAccount) {
          this.callbacks.connectAccount = [];
        }
        this.callbacks.connectAccount.push(callback);
      }

      ipcRenderer.send('mistAPI_requestAccount');
    },
    solidity: {
      version: String(packageJson.dependencies.solc).match(/\d+\.\d+\.\d+/)[0]
    },
    sounds: {
      bip: function playSound() {
        ipcRenderer.sendToHost(
          'mistAPI_sound',
          `file://${__dirname}/../../../sounds/bip.mp3`
        );
      },
      bloop: function playSound() {
        ipcRenderer.sendToHost(
          'mistAPI_sound',
          `file://${__dirname}/../../../sounds/bloop.mp3`
        );
      },
      invite: function playSound() {
        ipcRenderer.sendToHost(
          'mistAPI_sound',
          `file://${__dirname}/../../../sounds/invite.mp3`
        );
      }
    },
    menu: {
      entries: {},
      /**
            Sets the badge text for the apps menu button

            Example

                mist.menu.setBadge('Some Text')

            @method setBadge
            @param {String} text
            */
      setBadge(text) {
        ipcRenderer.sendToHost('mistAPI_setBadge', text);
      },
      /**
            Adds/Updates a menu entry

            Example

                mist.menu.add('tkrzU', {
                    name: 'My Meny Entry',
                    badge: 50,
                    position: 1,
                    selected: true
                }, function(){
                    // Router.go('/chat/1245');
                })

            @method add
            @param {String} id          The id of the menu, has to be the same accross page reloads.
            @param {Object} options     The menu options like {badge: 23, name: 'My Entry'}
            @param {Function} callback  Change the callback to be called when the menu is pressed.
            */
      add(id, options, callback) {
        const args = Array.prototype.slice.call(arguments);
        callback = _.isFunction(args[args.length - 1]) ? args.pop() : null;
        options = _.isObject(args[args.length - 1]) ? args.pop() : null;
        id =
          _.isString(args[args.length - 1]) || _.isFinite(args[args.length - 1])
            ? args.pop()
            : null;

        if (!filterAdd(options)) {
          return false;
        }

        const filteredId = prefix + filterId(id);

        // restricting to 100 menu entries
        if (
          !(filteredId in this.entries) &&
          Object.keys(this.entries).length >= MIST_SUBMENU_LIMIT
        ) {
          return false;
        }

        const entry = {
          id: filteredId || 'mist_defaultId',
          position: options.position,
          selected: !!options.selected,
          name: options.name,
          badge: options.badge
        };

        queue.push({
          action: 'addMenu',
          entry
        });

        if (callback) {
          entry.callback = callback;
        }

        this.entries[filteredId] = entry;
        return true;
      },
      /**
            Updates a menu entry from the mist sidebar.

            @method update
            @param {String} id          The id of the menu, has to be the same accross page reloads.
            @param {Object} options     The menu options like {badge: 23, name: 'My Entry'}
            @param {Function} callback  Change the callback to be called when the menu is pressed.
            */
      update() {
        this.add.apply(this, arguments);
      },
      /**
            Removes a menu entry from the mist sidebar.

            @method remove
            @param {String} id
            @param {String} id          The id of the menu, has to be the same accross page reloads.
            @param {Object} options     The menu options like {badge: 23, name: 'My Entry'}
            @param {Function} callback  Change the callback to be called when the menu is pressed.
            */
      remove(id) {
        const filteredId = prefix + filterId(id);

        delete this.entries[filteredId];

        queue.push({
          action: 'removeMenu',
          filteredId
        });
      },
      /**
            Marks a menu entry as selected

            @method select
            @param {String} id
            */
      select(id) {
        const filteredId = prefix + filterId(id);
        queue.push({ action: 'selectMenu', id: filteredId });

        for (const e in this.entries) {
          if ({}.hasOwnProperty.call(this.entries, e)) {
            this.entries[e].selected = e === filteredId;
          }
        }
      },
      /**
            Removes all menu entries.

            @method clear
            */
      clear() {
        this.entries = {};
        queue.push({ action: 'clearMenu' });
      }
    }
  };

  ipcRenderer.on('mistAPI_callMenuFunction', (e, id) => {
    if (mist.menu.entries[id] && mist.menu.entries[id].callback) {
      mist.menu.entries[id].callback();
    }
  });

  ipcRenderer.on('uiAction_windowMessage', (e, type, error, value) => {
    console.log('uiAction_windowMessage', type, error, value);
    if (mist.callbacks[type]) {
      mist.callbacks[type].forEach(cb => {
        cb(error, value);
      });
      delete mist.callbacks[type];
    }
  });

  // work up queue every 500ms
  setInterval(() => {
    if (queue.length > 0) {
      ipcRenderer.sendToHost('mistAPI_menuChanges', queue);
      queue = [];
    }
  }, 500);

  return mist;
};