/**
* @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;