Source: browser/client.js

  1. /**
  2. * @file Browser client connection management class
  3. * @author Shinichi Tomita <shinichi.tomita@gmail.com>
  4. */
  5. 'use strict';
  6. var events = require('events'),
  7. inherits = require('inherits'),
  8. qs = require('querystring'),
  9. _ = require('lodash/core'),
  10. Connection = require('../connection'),
  11. OAuth2 = require('../oauth2');
  12. /**
  13. * @private
  14. */
  15. function popupWin(url, w, h) {
  16. var left = (screen.width/2)-(w/2);
  17. var top = (screen.height/2)-(h/2);
  18. return window.open(url, null, 'location=yes,toolbar=no,status=no,menubar=no,width='+w+',height='+h+',top='+top+',left='+left);
  19. }
  20. function handleCallbackResponse() {
  21. var res = checkCallbackResponse();
  22. var state = localStorage.getItem('jsforce_state');
  23. if (res && state && res.body.state === state) {
  24. localStorage.removeItem('jsforce_state');
  25. var states = state.split('.');
  26. var prefix = states[0], promptType = states[1];
  27. var cli = new Client(prefix);
  28. if (res.success) {
  29. cli._storeTokens(res.body);
  30. location.hash = '';
  31. } else {
  32. cli._storeError(res.body);
  33. }
  34. if (promptType === 'popup') { window.close(); }
  35. return true;
  36. }
  37. }
  38. /**
  39. * @private
  40. */
  41. function checkCallbackResponse() {
  42. var params;
  43. if (window.location.hash) {
  44. params = qs.parse(window.location.hash.substring(1));
  45. if (params.access_token) {
  46. return { success: true, body: params };
  47. }
  48. } else if (window.location.search) {
  49. params = qs.parse(window.location.search.substring(1));
  50. if (params.error) {
  51. return { success: false, body: params };
  52. }
  53. }
  54. }
  55. /** @private **/
  56. var clientIdx = 0;
  57. /**
  58. * @class
  59. * @todo add document
  60. */
  61. var Client = function(prefix) {
  62. this._prefix = prefix || 'jsforce' + clientIdx++;
  63. this.connection = null;
  64. };
  65. inherits(Client, events.EventEmitter);
  66. /**
  67. *
  68. */
  69. Client.prototype.init = function(config) {
  70. if (handleCallbackResponse()) { return; }
  71. this.config = config;
  72. this.connection = new Connection(config);
  73. var tokens = this._getTokens();
  74. if (tokens) {
  75. this.connection.initialize(tokens);
  76. var self = this;
  77. setTimeout(function() {
  78. self.emit('connect', self.connection);
  79. }, 10);
  80. }
  81. };
  82. /**
  83. *
  84. */
  85. Client.prototype.login = function(options, callback) {
  86. if (_.isFunction(options)) {
  87. callback = options;
  88. options = {};
  89. }
  90. options = options || {};
  91. callback = callback || function(){ };
  92. _.extend(options, this.config);
  93. var self = this;
  94. this._prompt(options, callback);
  95. };
  96. Client.prototype._prompt = function(options, callback) {
  97. var self = this;
  98. var oauth2 = new OAuth2(options);
  99. var rand = Math.random().toString(36).substring(2);
  100. var state = [ this._prefix, "popup", rand ].join('.');
  101. localStorage.setItem("jsforce_state", state);
  102. var authzUrl = oauth2.getAuthorizationUrl({
  103. response_type: 'token',
  104. scope : options.scope,
  105. state: state
  106. });
  107. var size = options.size || {};
  108. var pw = popupWin(authzUrl, size.width || 912, size.height || 513);
  109. if (!pw) {
  110. state = [ this._prefix, "redirect", rand ].join('.');
  111. localStorage.setItem("jsforce_state", state);
  112. authzUrl = oauth2.getAuthorizationUrl({
  113. response_type: 'token',
  114. scope : options.scope,
  115. state: state
  116. });
  117. location.href = authzUrl;
  118. return;
  119. }
  120. self._removeTokens();
  121. var pid = setInterval(function() {
  122. try {
  123. if (!pw || pw.closed) {
  124. clearInterval(pid);
  125. var tokens = self._getTokens();
  126. if (tokens) {
  127. self.connection.initialize(tokens);
  128. self.emit('connect', self.connection);
  129. callback(null, { status: 'connect' });
  130. } else {
  131. var err = self._getError();
  132. if (err) {
  133. callback(new Error(err.error + ": " + err.error_description));
  134. } else {
  135. callback(null, { status: 'cancel' });
  136. }
  137. }
  138. }
  139. } catch(e) {}
  140. }, 1000);
  141. };
  142. /**
  143. *
  144. */
  145. Client.prototype.isLoggedIn = function() {
  146. return !!(this.connection && this.connection.accessToken);
  147. };
  148. /**
  149. *
  150. */
  151. Client.prototype.logout = function() {
  152. this.connection.logout();
  153. this._removeTokens();
  154. this.emit('disconnect');
  155. };
  156. /**
  157. * @private
  158. */
  159. Client.prototype._getTokens = function() {
  160. var regexp = new RegExp("(^|;\\s*)"+this._prefix+"_loggedin=true(;|$)");
  161. if (document.cookie.match(regexp)) {
  162. var issuedAt = Number(localStorage.getItem(this._prefix+'_issued_at'));
  163. if (Date.now() < issuedAt + 2 * 60 * 60 * 1000) { // 2 hours
  164. var userInfo;
  165. var idUrl = localStorage.getItem(this._prefix + '_id');
  166. if (idUrl) {
  167. var ids = idUrl.split('/');
  168. userInfo = { id: ids.pop(), organizationId: ids.pop(), url: idUrl };
  169. }
  170. return {
  171. accessToken: localStorage.getItem(this._prefix + '_access_token'),
  172. instanceUrl: localStorage.getItem(this._prefix + '_instance_url'),
  173. userInfo: userInfo
  174. };
  175. }
  176. }
  177. return null;
  178. };
  179. /**
  180. * @private
  181. */
  182. Client.prototype._storeTokens = function(params) {
  183. localStorage.setItem(this._prefix + '_access_token', params.access_token);
  184. localStorage.setItem(this._prefix + '_instance_url', params.instance_url);
  185. localStorage.setItem(this._prefix + '_issued_at', params.issued_at);
  186. localStorage.setItem(this._prefix + '_id', params.id);
  187. document.cookie = this._prefix + '_loggedin=true;';
  188. };
  189. /**
  190. * @private
  191. */
  192. Client.prototype._removeTokens = function() {
  193. localStorage.removeItem(this._prefix + '_access_token');
  194. localStorage.removeItem(this._prefix + '_instance_url');
  195. localStorage.removeItem(this._prefix + '_issued_at');
  196. localStorage.removeItem(this._prefix + '_id');
  197. document.cookie = this._prefix + '_loggedin=';
  198. };
  199. /**
  200. * @private
  201. */
  202. Client.prototype._getError = function() {
  203. try {
  204. var err = JSON.parse(localStorage.getItem(this._prefix + '_error'));
  205. localStorage.removeItem(this._prefix + '_error');
  206. return err;
  207. } catch(e) {}
  208. };
  209. /**
  210. * @private
  211. */
  212. Client.prototype._storeError = function(err) {
  213. localStorage.setItem(this._prefix + '_error', JSON.stringify(err));
  214. };
  215. /**
  216. *
  217. */
  218. module.exports = new Client();
  219. module.exports.Client = Client;