Source: modules/apps_cache.js

/**
 * AppsCache is a singleton that will encapsulate mozApps.mgmt inside and
 * expose neede API to callers. The reason why we need this is because
 * mozApps.mgmt.getAll() costs a lot and the better way to do so is cache
 * them inside this AppManager and automatically update it when install /
 * uninstall event is received.
 *
 * API:
 *
 * AppsCache.apps().then(function(apps) {
 *   // installed apps in FxOS
 * });
 *
 * @module AppsCache
 */
define(function(require) {
  'use strict';

  var mozApps = window.navigator.mozApps;
  var mozAppsMgmt = mozApps && mozApps.mgmt;

  var AppsCache = function() {
    this._initPromise = null;
    this._apps = [];
    this._eventHandlers = {};
    this._eventHandlers.oninstall = [];
    this._eventHandlers.onuninstall = [];
  };

  AppsCache.prototype = {
    /**
     * apps is a method that would return a promise that would return
     * cached apps.
     *
     * @access public
     * @memberOf AppsCache
     * @return {Promise}
     */
    apps: function am_apps() {
      var self = this;
      return this._init().then(function() {
        return self._apps;
      });
    },

    /**
     * Entry point
     *
     * @access private
     * @memberOf AppsCache
     * @return {Promise}
     */
    _init: function am__init() {
      var self = this;
      if (!this._initPromise) {
        this._initPromise = this._initApps().then(function() {
          self._initEvents();
        });
      }
      return this._initPromise;
    },

    /**
     * Init needed events
     *
     * @access private
     * @memberOf AppsCache
     */
    _initEvents: function am__initEvents() {
      var self = this;

      mozAppsMgmt.oninstall = function(evt) {
        var installedApp = evt.application;
        self._apps.push(installedApp);
        self._eventHandlers.oninstall.forEach(function(eventHandler) {
          eventHandler(evt);
        });
      };

      mozAppsMgmt.onuninstall = function(evt) {
        var manifestURL = evt.application.manifestURL;
        var removedAppIndex;
        // installed apps are normally in the end, so let's iterate
        // backward to find them quickly.
        for (var i = self._apps.length - 1; i >= 0; i--) {
          if (self._apps[i].manifestURL === manifestURL) {
            removedAppIndex = i;
            break;
          }
        }

        if (removedAppIndex) {
          self._apps.splice(removedAppIndex, 1);
          self._eventHandlers.onuninstall.forEach(function(eventHandler) {
            eventHandler(evt);
          });
        }
      };
    },

    /**
     * Init apps
     *
     * @access private
     * @memberOf AppsCache
     */
    _initApps: function am__initApps() {
      var self = this;
      var promise = new Promise(function(resolve, reject) {
        var req = mozAppsMgmt.getAll();
        req.onsuccess = function success(evt) {
          self._apps = evt.target.result;
          resolve(self._apps);
        };
        req.onerror = function error(evt) {
          console.log('failed to get installed apps');
          reject(req.error);
        };
      });
      return promise;
    },

    /**
     * Caller can add events when app is installed/uninstalled
     *
     * @access public
     * @param {String} eventName
     * @param {Function} callback
     * @memberOf AppsCache
     */
    addEventListener: function am_addEventListener(eventName, callback) {
      if (this._eventHandlers[eventName]) {
        this._eventHandlers[eventName].push(callback);
      }
    },

    /**
     * Caller can remove events when app is installed/uninstalled
     *
     * @access public
     * @param {String} eventName
     * @param {Function} callback
     * @memberOf AppsCache
     */
    removeEventListener: function am_removeEventListener(eventName, callback) {
      if (this._eventHandlers[eventName]) {
        var index = this._eventHandlers[eventName].indexOf(callback);
        if (index >= 0) {
          this._eventHandlers[eventName].splice(index, 1);
        }
      }
    },
  };

  var appsCache = new AppsCache();
  return appsCache;
});