Source: browser/client.js

/**
 * @file Browser client connection management class
 * @author Shinichi Tomita <shinichi.tomita@gmail.com>
 */

'use strict';

var events = require('events'),
    inherits = require('inherits'),
    qs = require('querystring'),
    _ = require('lodash/core'),
    Connection = require('../connection'),
    OAuth2 = require('../oauth2');

/**
 * @private
 */
function popupWin(url, w, h) {
  var left = (screen.width/2)-(w/2);
  var top = (screen.height/2)-(h/2);
  return window.open(url, null, 'location=yes,toolbar=no,status=no,menubar=no,width='+w+',height='+h+',top='+top+',left='+left);
}

function handleCallbackResponse() {
  var res = checkCallbackResponse();
  var state = localStorage.getItem('jsforce_state');
  if (res && state && res.body.state === state) {
    localStorage.removeItem('jsforce_state');
    var states = state.split('.');
    var prefix = states[0], promptType = states[1];
    var cli = new Client(prefix);
    if (res.success) {
      cli._storeTokens(res.body);
      location.hash = '';
    } else {
      cli._storeError(res.body);
    }
    if (promptType === 'popup') { window.close(); }
    return true;
  }
}

/**
 * @private
 */
function checkCallbackResponse() {
  var params;
  if (window.location.hash) {
    params = qs.parse(window.location.hash.substring(1));
    if (params.access_token) {
      return { success: true, body: params };
    }
  } else if (window.location.search) {
    params = qs.parse(window.location.search.substring(1));
    if (params.error) {
      return { success: false, body: params };
    }
  }
}

/** @private **/
var clientIdx = 0;


/**
 * @class
 * @todo add document
 */
var Client = function(prefix) {
  this._prefix = prefix || 'jsforce' + clientIdx++;
  this.connection = null;
};

inherits(Client, events.EventEmitter);

/**
 *
 */
Client.prototype.init = function(config) {
  if (handleCallbackResponse()) { return; }
  this.config = config;
  this.connection = new Connection(config);
  var tokens = this._getTokens();
  if (tokens) {
    this.connection.initialize(tokens);
    var self = this;
    setTimeout(function() {
      self.emit('connect', self.connection);
    }, 10);
  }
};

/**
 *
 */
Client.prototype.login = function(options, callback) {
  if (_.isFunction(options)) {
    callback = options;
    options = {};
  }
  options = options || {};
  callback = callback || function(){ };
  _.extend(options, this.config);
  var self = this;
  this._prompt(options, callback);
};


Client.prototype._prompt = function(options, callback) {
  var self = this;
  var oauth2 = new OAuth2(options);
  var rand = Math.random().toString(36).substring(2);
  var state = [ this._prefix, "popup", rand ].join('.');
  localStorage.setItem("jsforce_state", state);
  var authzUrl = oauth2.getAuthorizationUrl({
    response_type: 'token',
    scope : options.scope,
    state: state
  });
  var size = options.size || {};
  var pw = popupWin(authzUrl, size.width || 912, size.height || 513);
  if (!pw) {
    state = [ this._prefix, "redirect", rand ].join('.');
    localStorage.setItem("jsforce_state", state);
    authzUrl = oauth2.getAuthorizationUrl({
      response_type: 'token',
      scope : options.scope,
      state: state
    });
    location.href = authzUrl;
    return;
  }
  self._removeTokens();
  var pid = setInterval(function() {
    try {
      if (!pw || pw.closed) {
        clearInterval(pid);
        var tokens = self._getTokens();
        if (tokens) {
          self.connection.initialize(tokens);
          self.emit('connect', self.connection);
          callback(null, { status: 'connect' });
        } else {
          var err = self._getError();
          if (err) {
            callback(new Error(err.error + ": " + err.error_description));
          } else {
            callback(null, { status: 'cancel' });
          }
        }
      }
    } catch(e) {}
  }, 1000);
};

/**
 *
 */
Client.prototype.isLoggedIn = function() {
  return !!(this.connection && this.connection.accessToken);
};

/**
 *
 */
Client.prototype.logout = function() {
  this.connection.logout();
  this._removeTokens();
  this.emit('disconnect');
};

/**
 * @private
 */
Client.prototype._getTokens = function() {
  var regexp = new RegExp("(^|;\\s*)"+this._prefix+"_loggedin=true(;|$)");
  if (document.cookie.match(regexp)) {
    var issuedAt = Number(localStorage.getItem(this._prefix+'_issued_at'));
    if (Date.now() < issuedAt + 2 * 60 * 60 * 1000) { // 2 hours
      var userInfo;
      var idUrl = localStorage.getItem(this._prefix + '_id');
      if (idUrl) {
        var ids = idUrl.split('/');
        userInfo = { id: ids.pop(), organizationId: ids.pop(), url: idUrl };
      }
      return {
        accessToken: localStorage.getItem(this._prefix + '_access_token'),
        instanceUrl: localStorage.getItem(this._prefix + '_instance_url'),
        userInfo: userInfo
      };
    }
  }
  return null;
};

/**
 * @private
 */
Client.prototype._storeTokens = function(params) {
  localStorage.setItem(this._prefix + '_access_token', params.access_token);
  localStorage.setItem(this._prefix + '_instance_url', params.instance_url);
  localStorage.setItem(this._prefix + '_issued_at', params.issued_at);
  localStorage.setItem(this._prefix + '_id', params.id);
  document.cookie = this._prefix + '_loggedin=true;';
};

/**
 * @private
 */
Client.prototype._removeTokens = function() {
  localStorage.removeItem(this._prefix + '_access_token');
  localStorage.removeItem(this._prefix + '_instance_url');
  localStorage.removeItem(this._prefix + '_issued_at');
  localStorage.removeItem(this._prefix + '_id');
  document.cookie = this._prefix + '_loggedin=';
};

/**
 * @private
 */
Client.prototype._getError = function() {
  try {
    var err = JSON.parse(localStorage.getItem(this._prefix + '_error'));
    localStorage.removeItem(this._prefix + '_error');
    return err;
  } catch(e) {}
};

/**
 * @private
 */
Client.prototype._storeError = function(err) {
  localStorage.setItem(this._prefix + '_error', JSON.stringify(err));
};

/**
 *
 */
module.exports = new Client();

module.exports.Client = Client;