Source: connection.js

  1. /*global Buffer */
  2. /**
  3. * @file Connection class to keep the API session information and manage requests
  4. * @author Shinichi Tomita <shinichi.tomita@gmail.com>
  5. */
  6. 'use strict';
  7. var events = require('events'),
  8. inherits = require('inherits'),
  9. _ = require('lodash/core'),
  10. Promise = require('./promise'),
  11. Logger = require('./logger'),
  12. OAuth2 = require('./oauth2'),
  13. Query = require('./query'),
  14. SObject = require('./sobject'),
  15. QuickAction = require('./quick-action'),
  16. HttpApi = require('./http-api'),
  17. Transport = require('./transport'),
  18. Process = require('./process'),
  19. Cache = require('./cache');
  20. var defaults = {
  21. loginUrl: "https://login.salesforce.com",
  22. instanceUrl: "",
  23. version: "36.0"
  24. };
  25. /**
  26. * Connection class to keep the API session information and manage requests
  27. *
  28. * @constructor
  29. * @extends events.EventEmitter
  30. * @param {Object} [options] - Connection options
  31. * @param {OAuth2|Object} [options.oauth2] - OAuth2 instance or options to be passed to OAuth2 constructor
  32. * @param {String} [options.logLevel] - Output logging level (DEBUG|INFO|WARN|ERROR|FATAL)
  33. * @param {String} [options.version] - Salesforce API Version (without "v" prefix)
  34. * @param {Number} [options.maxRequest] - Max number of requests allowed in parallel call
  35. * @param {String} [options.loginUrl] - Salesforce Login Server URL (e.g. https://login.salesforce.com/)
  36. * @param {String} [options.instanceUrl] - Salesforce Instance URL (e.g. https://na1.salesforce.com/)
  37. * @param {String} [options.serverUrl] - Salesforce SOAP service endpoint URL (e.g. https://na1.salesforce.com/services/Soap/u/28.0)
  38. * @param {String} [options.accessToken] - Salesforce OAuth2 access token
  39. * @param {String} [options.sessionId] - Salesforce session ID
  40. * @param {String} [options.refreshToken] - Salesforce OAuth2 refresh token
  41. * @param {String|Object} [options.signedRequest] - Salesforce Canvas signed request (Raw Base64 string, JSON string, or deserialized JSON)
  42. * @param {String} [options.proxyUrl] - Cross-domain proxy server URL, used in browser client, non Visualforce app.
  43. * @param {Object} [options.callOptions] - Call options used in each SOAP/REST API request. See manual.
  44. */
  45. var Connection = module.exports = function(options) {
  46. options = options || {};
  47. this._logger = new Logger(options.logLevel);
  48. var oauth2 = options.oauth2 || {
  49. loginUrl : options.loginUrl,
  50. clientId : options.clientId,
  51. clientSecret : options.clientSecret,
  52. redirectUri : options.redirectUri
  53. };
  54. /**
  55. * OAuth2 object
  56. * @member {OAuth2} Connection#oauth2
  57. */
  58. this.oauth2 = oauth2 = oauth2 instanceof OAuth2 ? oauth2 : new OAuth2(oauth2);
  59. this.loginUrl = options.loginUrl || oauth2.loginUrl || defaults.loginUrl;
  60. this.version = options.version || defaults.version;
  61. this.maxRequest = options.maxRequest || this.maxRequest || 10;
  62. /** @private */
  63. this._transport =
  64. options.proxyUrl ? new Transport.ProxyTransport(options.proxyUrl) : new Transport();
  65. this.callOptions = options.callOptions;
  66. /*
  67. * Fire connection:new event to notify jsforce plugin modules
  68. */
  69. var jsforce = require('./core');
  70. jsforce.emit('connection:new', this);
  71. /**
  72. * Streaming API object
  73. * @member {Streaming} Connection#streaming
  74. */
  75. // this.streaming = new Streaming(this);
  76. /**
  77. * Bulk API object
  78. * @member {Bulk} Connection#bulk
  79. */
  80. // this.bulk = new Bulk(this);
  81. /**
  82. * Tooling API object
  83. * @member {Tooling} Connection#tooling
  84. */
  85. // this.tooling = new Tooling(this);
  86. /**
  87. * Analytics API object
  88. * @member {Analytics} Connection#analytics
  89. */
  90. // this.analytics = new Analytics(this);
  91. /**
  92. * Chatter API object
  93. * @member {Chatter} Connection#chatter
  94. */
  95. // this.chatter = new Chatter(this);
  96. /**
  97. * Metadata API object
  98. * @member {Metadata} Connection#metadata
  99. */
  100. // this.metadata = new Metadata(this);
  101. /**
  102. * SOAP API object
  103. * @member {SoapApi} Connection#soap
  104. */
  105. // this.soap = new SoapApi(this);
  106. /**
  107. * Apex REST API object
  108. * @member {Apex} Connection#apex
  109. */
  110. // this.apex = new Apex(this);
  111. /**
  112. * @member {Process} Connection#process
  113. */
  114. this.process = new Process(this);
  115. /**
  116. * Cache object for result
  117. * @member {Cache} Connection#cache
  118. */
  119. this.cache = new Cache();
  120. // Allow to delegate connection refresh to outer function
  121. var self = this;
  122. var refreshFn = options.refreshFn;
  123. if (!refreshFn && this.oauth2.clientId && this.oauth2.clientSecret) {
  124. refreshFn = oauthRefreshFn;
  125. }
  126. if (refreshFn) {
  127. this._refreshDelegate = new HttpApi.SessionRefreshDelegate(this, refreshFn);
  128. }
  129. var cacheOptions = {
  130. key: function(type) { return type ? "describe." + type : "describe"; }
  131. };
  132. this.describe$ = this.cache.makeCacheable(this.describe, this, cacheOptions);
  133. this.describe = this.cache.makeResponseCacheable(this.describe, this, cacheOptions);
  134. this.describeSObject$ = this.describe$;
  135. this.describeSObject = this.describe;
  136. cacheOptions = { key: 'describeGlobal' };
  137. this.describeGlobal$ = this.cache.makeCacheable(this.describeGlobal, this, cacheOptions);
  138. this.describeGlobal = this.cache.makeResponseCacheable(this.describeGlobal, this, cacheOptions);
  139. this.initialize(options);
  140. };
  141. inherits(Connection, events.EventEmitter);
  142. /**
  143. * Initialize connection.
  144. *
  145. * @protected
  146. * @param {Object} options - Initialization options
  147. * @param {String} [options.instanceUrl] - Salesforce Instance URL (e.g. https://na1.salesforce.com/)
  148. * @param {String} [options.serverUrl] - Salesforce SOAP service endpoint URL (e.g. https://na1.salesforce.com/services/Soap/u/28.0)
  149. * @param {String} [options.accessToken] - Salesforce OAuth2 access token
  150. * @param {String} [options.sessionId] - Salesforce session ID
  151. * @param {String} [options.refreshToken] - Salesforce OAuth2 refresh token
  152. * @param {String|Object} [options.signedRequest] - Salesforce Canvas signed request (Raw Base64 string, JSON string, or deserialized JSON)
  153. * @param {UserInfo} [options.userInfo] - Logged in user information
  154. */
  155. Connection.prototype.initialize = function(options) {
  156. if (!options.instanceUrl && options.serverUrl) {
  157. options.instanceUrl = options.serverUrl.split('/').slice(0, 3).join('/');
  158. }
  159. this.instanceUrl = options.instanceUrl || options.serverUrl || this.instanceUrl || defaults.instanceUrl;
  160. this.accessToken = options.sessionId || options.accessToken || this.accessToken;
  161. this.refreshToken = options.refreshToken || this.refreshToken;
  162. if (this.refreshToken && !this._refreshDelegate) {
  163. throw new Error("Refresh token is specified without oauth2 client information or refresh function");
  164. }
  165. this.signedRequest = options.signedRequest && parseSignedRequest(options.signedRequest);
  166. if (this.signedRequest) {
  167. this.accessToken = this.signedRequest.client.oauthToken;
  168. }
  169. if (options.userInfo) {
  170. this.userInfo = options.userInfo;
  171. }
  172. this.limitInfo = {};
  173. this.sobjects = {};
  174. this.cache.clear();
  175. this.cache.get('describeGlobal').on('value', _.bind(function(res) {
  176. if (res.result) {
  177. var types = _.map(res.result.sobjects, function(so) { return so.name; });
  178. types.forEach(this.sobject, this);
  179. }
  180. }, this));
  181. if (this.tooling) {
  182. this.tooling.initialize();
  183. }
  184. this._sessionType = options.sessionId ? "soap" : "oauth2";
  185. };
  186. /** @private **/
  187. function oauthRefreshFn(conn, callback) {
  188. conn.oauth2.refreshToken(conn.refreshToken, function(err, res) {
  189. if (err) { return callback(err); }
  190. var userInfo = parseIdUrl(res.id);
  191. conn.initialize({
  192. instanceUrl : res.instance_url,
  193. accessToken : res.access_token,
  194. userInfo : userInfo
  195. });
  196. callback(null, res.access_token, res);
  197. });
  198. }
  199. /** @private **/
  200. function parseSignedRequest(sr) {
  201. if (_.isString(sr)) {
  202. if (sr[0] === '{') { // might be JSON
  203. return JSON.parse(sr);
  204. } else { // might be original base64-encoded signed request
  205. var msg = sr.split('.').pop(); // retrieve latter part
  206. var json = new Buffer(msg, 'base64').toString('utf-8');
  207. return JSON.parse(json);
  208. }
  209. return null;
  210. }
  211. return sr;
  212. }
  213. /** @private **/
  214. Connection.prototype._baseUrl = function() {
  215. return [ this.instanceUrl, "services/data", "v" + this.version ].join('/');
  216. };
  217. /**
  218. * Convert path to absolute url
  219. * @private
  220. */
  221. Connection.prototype._normalizeUrl = function(url) {
  222. if (url[0] === '/') {
  223. if (url.indexOf('/services/') === 0) {
  224. return this.instanceUrl + url;
  225. } else {
  226. return this._baseUrl() + url;
  227. }
  228. } else {
  229. return url;
  230. }
  231. };
  232. /**
  233. * Send REST API request with given HTTP request info, with connected session information.
  234. *
  235. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  236. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  237. * , or relative path from version root ('/sobjects/Account/describe').
  238. *
  239. * @param {String|Object} request - HTTP request object or URL to GET request
  240. * @param {String} request.method - HTTP method URL to send HTTP request
  241. * @param {String} request.url - URL to send HTTP request
  242. * @param {Object} [request.headers] - HTTP request headers in hash object (key-value)
  243. * @param {Object} [options] - HTTP API request options
  244. * @param {Callback.<Object>} [callback] - Callback function
  245. * @returns {Promise.<Object>}
  246. */
  247. Connection.prototype.request = function(request, options, callback) {
  248. if (typeof options === 'function') {
  249. callback = options;
  250. options = null;
  251. }
  252. options = options || {};
  253. var self = this;
  254. // if request is simple string, regard it as url in GET method
  255. if (_.isString(request)) {
  256. request = { method: 'GET', url: request };
  257. }
  258. // if url is given in relative path, prepend base url or instance url before.
  259. request.url = this._normalizeUrl(request.url);
  260. var httpApi = new HttpApi(this, options);
  261. // log api usage and its quota
  262. httpApi.on('response', function(response) {
  263. if (response.headers && response.headers["sforce-limit-info"]) {
  264. var apiUsage = response.headers["sforce-limit-info"].match(/api\-usage=(\d+)\/(\d+)/);
  265. if (apiUsage) {
  266. self.limitInfo = {
  267. apiUsage: {
  268. used: parseInt(apiUsage[1], 10),
  269. limit: parseInt(apiUsage[2], 10)
  270. }
  271. };
  272. }
  273. }
  274. });
  275. return httpApi.request(request).thenCall(callback);
  276. };
  277. /**
  278. * Send HTTP GET request
  279. *
  280. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  281. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  282. * , or relative path from version root ('/sobjects/Account/describe').
  283. *
  284. * @param {String} url - Endpoint URL to request HTTP GET
  285. * @param {Object} [options] - HTTP API request options
  286. * @param {Callback.<Object>} [callback] - Callback function
  287. * @returns {Promise.<Object>}
  288. */
  289. Connection.prototype.requestGet = function(url, options, callback) {
  290. var request = {
  291. method: "GET",
  292. url: url
  293. };
  294. return this.request(request, options, callback);
  295. };
  296. /**
  297. * Send HTTP POST request with JSON body, with connected session information
  298. *
  299. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  300. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  301. * , or relative path from version root ('/sobjects/Account/describe').
  302. *
  303. * @param {String} url - Endpoint URL to request HTTP POST
  304. * @param {Object} body - Any JS object which can be serialized to JSON
  305. * @param {Object} [options] - HTTP API request options
  306. * @param {Callback.<Object>} [callback] - Callback function
  307. * @returns {Promise.<Object>}
  308. */
  309. Connection.prototype.requestPost = function(url, body, options, callback) {
  310. var request = {
  311. method: "POST",
  312. url: url,
  313. body: JSON.stringify(body),
  314. headers: { "content-type": "application/json" }
  315. };
  316. return this.request(request, options, callback);
  317. };
  318. /**
  319. * Send HTTP PUT request with JSON body, with connected session information
  320. *
  321. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  322. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  323. * , or relative path from version root ('/sobjects/Account/describe').
  324. *
  325. * @param {String} url - Endpoint URL to request HTTP PUT
  326. * @param {Object} body - Any JS object which can be serialized to JSON
  327. * @param {Object} [options] - HTTP API request options
  328. * @param {Callback.<Object>} [callback] - Callback function
  329. * @returns {Promise.<Object>}
  330. */
  331. Connection.prototype.requestPut = function(url, body, options, callback) {
  332. var request = {
  333. method: "PUT",
  334. url: url,
  335. body: JSON.stringify(body),
  336. headers: { "content-type": "application/json" }
  337. };
  338. return this.request(request, options, callback);
  339. };
  340. /**
  341. * Send HTTP PATCH request with JSON body
  342. *
  343. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  344. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  345. * , or relative path from version root ('/sobjects/Account/describe').
  346. *
  347. * @param {String} url - Endpoint URL to request HTTP PATCH
  348. * @param {Object} body - Any JS object which can be serialized to JSON
  349. * @param {Object} [options] - HTTP API request options
  350. * @param {Callback.<Object>} [callback] - Callback function
  351. * @returns {Promise.<Object>}
  352. */
  353. Connection.prototype.requestPatch = function(url, body, options, callback) {
  354. var request = {
  355. method: "PATCH",
  356. url: url,
  357. body: JSON.stringify(body),
  358. headers: { "content-type": "application/json" }
  359. };
  360. return this.request(request, options, callback);
  361. };
  362. /**
  363. * Send HTTP DELETE request
  364. *
  365. * Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
  366. * , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
  367. * , or relative path from version root ('/sobjects/Account/describe').
  368. *
  369. * @param {String} url - Endpoint URL to request HTTP DELETE
  370. * @param {Object} [options] - HTTP API request options
  371. * @param {Callback.<Object>} [callback] - Callback function
  372. * @returns {Promise.<Object>}
  373. */
  374. Connection.prototype.requestDelete = function(url, options, callback) {
  375. var request = {
  376. method: "DELETE",
  377. url: url
  378. };
  379. return this.request(request, options, callback);
  380. };
  381. /** @private */
  382. function formatDate(date) {
  383. function pad(number) {
  384. if (number < 10) {
  385. return '0' + number;
  386. }
  387. return number;
  388. }
  389. return date.getUTCFullYear() +
  390. '-' + pad(date.getUTCMonth() + 1) +
  391. '-' + pad(date.getUTCDate()) +
  392. 'T' + pad(date.getUTCHours()) +
  393. ':' + pad(date.getUTCMinutes()) +
  394. ':' + pad(date.getUTCSeconds()) +
  395. '+00:00';
  396. }
  397. /** @private **/
  398. function parseIdUrl(idUrl) {
  399. var idUrls = idUrl.split("/");
  400. var userId = idUrls.pop(), orgId = idUrls.pop();
  401. return {
  402. id: userId,
  403. organizationId: orgId,
  404. url: idUrl
  405. };
  406. }
  407. /**
  408. * @callback Callback
  409. * @type {Function}
  410. * @param {Error} err - Callback error
  411. * @param {T} response - Callback response
  412. * @template T
  413. */
  414. /**
  415. * @typedef {Object} QueryResult
  416. * @prop {Boolean} done - Flag if the query is fetched all records or not
  417. * @prop {String} [nextRecordsUrl] - URL locator for next record set, (available when done = false)
  418. * @prop {Number} totalSize - Total size for query
  419. * @prop {Array.<Record>} [records] - Array of records fetched
  420. */
  421. /**
  422. * Execute query by using SOQL
  423. *
  424. * @param {String} soql - SOQL string
  425. * @param {Callback.<QueryResult>} [callback] - Callback function
  426. * @returns {Query.<QueryResult>}
  427. */
  428. Connection.prototype.query = function(soql, callback) {
  429. var query = new Query(this, soql);
  430. if (callback) {
  431. query.run(callback);
  432. }
  433. return query;
  434. };
  435. /**
  436. * Execute query by using SOQL, including deleted records
  437. *
  438. * @param {String} soql - SOQL string
  439. * @param {Callback.<QueryResult>} [callback] - Callback function
  440. * @returns {Query.<QueryResult>}
  441. */
  442. Connection.prototype.queryAll = function(soql, callback) {
  443. var query = new Query(this, soql);
  444. query.scanAll(true);
  445. if (callback) {
  446. query.run(callback);
  447. }
  448. return query;
  449. };
  450. /**
  451. * Query next record set by using query locator
  452. *
  453. * @param {String} locator - Next record set locator
  454. * @param {Callback.<QueryResult>} [callback] - Callback function
  455. * @returns {Query.<QueryResult>}
  456. */
  457. Connection.prototype.queryMore = function(locator, callback) {
  458. var query = new Query(this, null, locator);
  459. if (callback) {
  460. query.run(callback);
  461. }
  462. return query;
  463. };
  464. /**
  465. * Retrieve specified records
  466. *
  467. * @param {String} type - SObject Type
  468. * @param {String|Array.<String>} ids - A record ID or array of record IDs
  469. * @param {Object} [options] - Options for rest api.
  470. * @param {Callback.<Record|Array.<Record>>} [callback] - Callback function
  471. * @returns {Promise.<Record|Array.<Record>>}
  472. */
  473. Connection.prototype.retrieve = function(type, ids, options, callback) {
  474. if (typeof options === 'function') {
  475. callback = options;
  476. options = {};
  477. }
  478. var self = this;
  479. var isArray = _.isArray(ids);
  480. ids = isArray ? ids : [ ids ];
  481. if (ids.length > self.maxRequest) {
  482. return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
  483. }
  484. return Promise.all(
  485. _.map(ids, function(id) {
  486. if (!id) { return Promise.reject(new Error('Invalid record ID. Specify valid record ID value')).thenCall(callback); }
  487. var url = [ self._baseUrl(), "sobjects", type, id ].join('/');
  488. return self.request(url);
  489. })
  490. ).then(function(results) {
  491. return !isArray && _.isArray(results) ? results[0] : results;
  492. }).thenCall(callback);
  493. };
  494. /**
  495. * @typedef RecordResult
  496. * @prop {Boolean} success - The result is succeessful or not
  497. * @prop {String} [id] - Record ID
  498. * @prop {Array.<String>} [errors] - Errors (available when success = false)
  499. */
  500. /**
  501. * Synonym of Connection#create()
  502. *
  503. * @method Connection#insert
  504. * @param {String} type - SObject Type
  505. * @param {Object|Array.<Object>} records - A record or array of records to create
  506. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
  507. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  508. */
  509. /**
  510. * Create records
  511. *
  512. * @method Connection#create
  513. * @param {String} type - SObject Type
  514. * @param {Record|Array.<Record>} records - A record or array of records to create
  515. * @param {Object} [options] - Options for rest api.
  516. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
  517. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  518. */
  519. Connection.prototype.insert =
  520. Connection.prototype.create = function(type, records, options, callback) {
  521. if (!_.isString(type)) {
  522. // reverse order
  523. callback = options;
  524. options = records;
  525. records = type;
  526. type = null;
  527. }
  528. if (typeof options === 'function') {
  529. callback = options;
  530. options = {};
  531. }
  532. options = options || {};
  533. var self = this;
  534. var isArray = _.isArray(records);
  535. records = isArray ? records : [ records ];
  536. if (records.length > self.maxRequest) {
  537. return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
  538. }
  539. return Promise.all(
  540. _.map(records, function(record) {
  541. var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
  542. if (!sobjectType) {
  543. throw new Error('No SObject Type defined in record');
  544. }
  545. record = _.clone(record);
  546. delete record.Id;
  547. delete record.type;
  548. delete record.attributes;
  549. var url = [ self._baseUrl(), "sobjects", sobjectType ].join('/');
  550. return self.request({
  551. method : 'POST',
  552. url : url,
  553. body : JSON.stringify(record),
  554. headers : _.defaults(options.headers || {}, {
  555. "Content-Type" : "application/json"
  556. })
  557. });
  558. })
  559. ).then(function(results) {
  560. return !isArray && _.isArray(results) ? results[0] : results;
  561. }).thenCall(callback);
  562. };
  563. /**
  564. * Update records
  565. *
  566. * @param {String} type - SObject Type
  567. * @param {Record|Array.<Record>} records - A record or array of records to update
  568. * @param {Object} [options] - Options for rest api.
  569. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
  570. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  571. */
  572. Connection.prototype.update = function(type, records, options, callback) {
  573. if (!_.isString(type)) {
  574. // reverse order
  575. callback = options;
  576. options = records;
  577. records = type;
  578. type = null;
  579. }
  580. if (typeof options === 'function') {
  581. callback = options;
  582. options = {};
  583. }
  584. options = options || {};
  585. var self = this;
  586. var isArray = _.isArray(records);
  587. records = isArray ? records : [ records ];
  588. if (records.length > self.maxRequest) {
  589. return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
  590. }
  591. return Promise.all(
  592. _.map(records, function(record) {
  593. var id = record.Id;
  594. if (!id) {
  595. throw new Error('Record id is not found in record.');
  596. }
  597. var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
  598. if (!sobjectType) {
  599. throw new Error('No SObject Type defined in record');
  600. }
  601. record = _.clone(record);
  602. delete record.Id;
  603. delete record.type;
  604. delete record.attributes;
  605. var url = [ self._baseUrl(), "sobjects", sobjectType, id ].join('/');
  606. return self.request({
  607. method : 'PATCH',
  608. url : url,
  609. body : JSON.stringify(record),
  610. headers : _.defaults(options.headers || {}, {
  611. "Content-Type" : "application/json"
  612. })
  613. }, {
  614. noContentResponse: { id : id, success : true, errors : [] }
  615. });
  616. })
  617. ).then(function(results) {
  618. return !isArray && _.isArray(results) ? results[0] : results;
  619. }).thenCall(callback);
  620. };
  621. /**
  622. * Upsert records
  623. *
  624. * @param {String} type - SObject Type
  625. * @param {Record|Array.<Record>} records - Record or array of records to upsert
  626. * @param {String} extIdField - External ID field name
  627. * @param {Object} [options] - Options for rest api.
  628. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
  629. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  630. */
  631. Connection.prototype.upsert = function(type, records, extIdField, options, callback) {
  632. // You can omit "type" argument, when the record includes type information.
  633. if (!_.isString(type)) {
  634. // reverse order
  635. callback = options;
  636. options = extIdField;
  637. extIdField = records;
  638. records = type;
  639. type = null;
  640. }
  641. if (typeof options === 'function') {
  642. callback = options;
  643. options = {};
  644. }
  645. options = options || {};
  646. var self = this;
  647. var isArray = _.isArray(records);
  648. records = isArray ? records : [ records ];
  649. if (records.length > self.maxRequest) {
  650. return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
  651. }
  652. return Promise.all(
  653. _.map(records, function(record) {
  654. var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
  655. var extId = record[extIdField];
  656. if (!extId) {
  657. return Promise.reject(new Error('External ID is not defined in the record'));
  658. }
  659. record = _.clone(record);
  660. delete record[extIdField];
  661. delete record.type;
  662. delete record.attributes;
  663. var url = [ self._baseUrl(), "sobjects", sobjectType, extIdField, extId ].join('/');
  664. return self.request({
  665. method : 'PATCH',
  666. url : url,
  667. body : JSON.stringify(record),
  668. headers : _.defaults(options.headers || {}, {
  669. "Content-Type" : "application/json"
  670. })
  671. }, {
  672. noContentResponse: { success : true, errors : [] }
  673. });
  674. })
  675. ).then(function(results) {
  676. return !isArray && _.isArray(results) ? results[0] : results;
  677. }).thenCall(callback);
  678. };
  679. /**
  680. * Synonym of Connection#destroy()
  681. *
  682. * @method Connection#delete
  683. * @param {String} type - SObject Type
  684. * @param {String|Array.<String>} ids - A ID or array of IDs to delete
  685. * @param {Object} [options] - Options for rest api.
  686. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
  687. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  688. */
  689. /**
  690. * Synonym of Connection#destroy()
  691. *
  692. * @method Connection#del
  693. * @param {String} type - SObject Type
  694. * @param {String|Array.<String>} ids - A ID or array of IDs to delete
  695. * @param {Object} [options] - Options for rest api.
  696. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
  697. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  698. */
  699. /**
  700. * Delete records
  701. *
  702. * @method Connection#destroy
  703. * @param {String} type - SObject Type
  704. * @param {String|Array.<String>} ids - A ID or array of IDs to delete
  705. * @param {Object} [options] - Options for rest api.
  706. * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
  707. * @returns {Promise.<RecordResult|Array.<RecordResult>>}
  708. */
  709. Connection.prototype["delete"] =
  710. Connection.prototype.del =
  711. Connection.prototype.destroy = function(type, ids, options, callback) {
  712. if (typeof options === 'function') {
  713. callback = options;
  714. options = {};
  715. }
  716. options = options || {};
  717. var self = this;
  718. var isArray = _.isArray(ids);
  719. ids = isArray ? ids : [ ids ];
  720. if (ids.length > self.maxRequest) {
  721. return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
  722. }
  723. return Promise.all(
  724. _.map(ids, function(id) {
  725. var url = [ self._baseUrl(), "sobjects", type, id ].join('/');
  726. return self.request({
  727. method : 'DELETE',
  728. url : url,
  729. headers: options.headers || null
  730. }, {
  731. noContentResponse: { id : id, success : true, errors : [] }
  732. });
  733. })
  734. ).then(function(results) {
  735. return !isArray && _.isArray(results) ? results[0] : results;
  736. }).thenCall(callback);
  737. };
  738. /**
  739. * Execute search by SOSL
  740. *
  741. * @param {String} sosl - SOSL string
  742. * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
  743. * @returns {Promise.<Array.<RecordResult>>}
  744. */
  745. Connection.prototype.search = function(sosl, callback) {
  746. var url = this._baseUrl() + "/search?q=" + encodeURIComponent(sosl);
  747. return this.request(url).thenCall(callback);
  748. };
  749. /**
  750. * Result returned by describeSObject call
  751. *
  752. * @typedef {Object} DescribeSObjectResult
  753. */
  754. /**
  755. * Synonym of Connection#describe()
  756. *
  757. * @method Connection#describeSObject
  758. * @param {String} type - SObject Type
  759. * @param {Callback.<DescribeSObjectResult>} [callback] - Callback function
  760. * @returns {Promise.<DescribeSObjectResult>}
  761. */
  762. /**
  763. * Describe SObject metadata
  764. *
  765. * @method Connection#describe
  766. * @param {String} type - SObject Type
  767. * @param {Callback.<DescribeSObjectResult>} [callback] - Callback function
  768. * @returns {Promise.<DescribeSObjectResult>}
  769. */
  770. Connection.prototype.describe =
  771. Connection.prototype.describeSObject = function(type, callback) {
  772. var url = [ this._baseUrl(), "sobjects", type, "describe" ].join('/');
  773. return this.request(url).thenCall(callback);
  774. };
  775. /**
  776. * Result returned by describeGlobal call
  777. *
  778. * @typedef {Object} DescribeGlobalResult
  779. */
  780. /**
  781. * Describe global SObjects
  782. *
  783. * @param {Callback.<DescribeGlobalResult>} [callback] - Callback function
  784. * @returns {Promise.<DescribeGlobalResult>}
  785. */
  786. Connection.prototype.describeGlobal = function(callback) {
  787. var url = this._baseUrl() + "/sobjects";
  788. return this.request(url).thenCall(callback);
  789. };
  790. /**
  791. * Get SObject instance
  792. *
  793. * @param {String} type - SObject Type
  794. * @returns {SObject}
  795. */
  796. Connection.prototype.sobject = function(type) {
  797. this.sobjects = this.sobjects || {};
  798. var sobject = this.sobjects[type] =
  799. this.sobjects[type] || new SObject(this, type);
  800. return sobject;
  801. };
  802. /**
  803. * Get identity information of current user
  804. *
  805. * @param {Callback.<IdentityInfo>} [callback] - Callback function
  806. * @returns {Promise.<IdentityInfo>}
  807. */
  808. Connection.prototype.identity = function(callback) {
  809. var self = this;
  810. var idUrl = this.userInfo && this.userInfo.url;
  811. return Promise.resolve(
  812. idUrl ?
  813. { identity: idUrl } :
  814. this.request(this._baseUrl())
  815. ).then(function(res) {
  816. var url = res.identity;
  817. url += '?format=json&oauth_token=' + encodeURIComponent(self.accessToken);
  818. return self.request(url, null, { jsonp : 'callback' });
  819. }).then(function(res) {
  820. self.userInfo = {
  821. id: res.user_id,
  822. organizationId: res.organization_id,
  823. url: res.id
  824. };
  825. return res;
  826. }).thenCall(callback);
  827. };
  828. /**
  829. * @typedef UserInfo
  830. * @prop {String} id - User ID
  831. * @prop {String} organizationId - Organization ID
  832. * @prop {String} url - Identity URL of the user
  833. */
  834. /**
  835. * Authorize (using oauth2 web server flow)
  836. *
  837. * @param {String} code - Authorization code
  838. * @param {Callback.<UserInfo>} [callback] - Callback function
  839. * @returns {Promise.<UserInfo>}
  840. */
  841. Connection.prototype.authorize = function(code, callback) {
  842. var self = this;
  843. var logger = this._logger;
  844. return this.oauth2.requestToken(code).then(function(res) {
  845. logger.debug("OAuth2 token response = " + JSON.stringify(res));
  846. var userInfo = parseIdUrl(res.id);
  847. self.initialize({
  848. instanceUrl : res.instance_url,
  849. accessToken : res.access_token,
  850. refreshToken : res.refresh_token,
  851. userInfo: userInfo
  852. });
  853. logger.debug("<login> completed. user id = " + userInfo.id + ", org id = " + userInfo.organizationId);
  854. return userInfo;
  855. }).thenCall(callback);
  856. };
  857. /**
  858. * Login to Salesforce
  859. *
  860. * @param {String} username - Salesforce username
  861. * @param {String} password - Salesforce password (and security token, if required)
  862. * @param {Callback.<UserInfo>} [callback] - Callback function
  863. * @returns {Promise.<UserInfo>}
  864. */
  865. Connection.prototype.login = function(username, password, callback) {
  866. // register refreshDelegate for session expiration
  867. this._refreshDelegate = new HttpApi.SessionRefreshDelegate(this, createUsernamePasswordRefreshFn(username, password));
  868. if (this.oauth2 && this.oauth2.clientId && this.oauth2.clientSecret) {
  869. return this.loginByOAuth2(username, password, callback);
  870. } else {
  871. return this.loginBySoap(username, password, callback);
  872. }
  873. };
  874. /** @private **/
  875. function createUsernamePasswordRefreshFn(username, password) {
  876. return function(conn, callback) {
  877. conn.login(username, password, function(err) {
  878. if (err) { return callback(err); }
  879. callback(null, conn.accessToken);
  880. });
  881. };
  882. }
  883. /**
  884. * Login by OAuth2 username & password flow
  885. *
  886. * @param {String} username - Salesforce username
  887. * @param {String} password - Salesforce password (and security token, if required)
  888. * @param {Callback.<UserInfo>} [callback] - Callback function
  889. * @returns {Promise.<UserInfo>}
  890. */
  891. Connection.prototype.loginByOAuth2 = function(username, password, callback) {
  892. var self = this;
  893. var logger = this._logger;
  894. return this.oauth2.authenticate(username, password).then(function(res) {
  895. logger.debug("OAuth2 token response = " + JSON.stringify(res));
  896. var userInfo = parseIdUrl(res.id);
  897. self.initialize({
  898. instanceUrl : res.instance_url,
  899. accessToken : res.access_token,
  900. userInfo: userInfo
  901. });
  902. logger.debug("<login> completed. user id = " + userInfo.id + ", org id = " + userInfo.organizationId);
  903. return userInfo;
  904. }).thenCall(callback);
  905. };
  906. /**
  907. * @private
  908. */
  909. function esc(str) {
  910. return str && String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;')
  911. .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  912. }
  913. /**
  914. * Login by SOAP web service API
  915. *
  916. * @param {String} username - Salesforce username
  917. * @param {String} password - Salesforce password (and security token, if required)
  918. * @param {Callback.<UserInfo>} [callback] - Callback function
  919. * @returns {Promise.<UserInfo>}
  920. */
  921. Connection.prototype.loginBySoap = function(username, password, callback) {
  922. var self = this;
  923. var logger = this._logger;
  924. var body = [
  925. '<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
  926. '<se:Header/>',
  927. '<se:Body>',
  928. '<login xmlns="urn:partner.soap.sforce.com">',
  929. '<username>' + esc(username) + '</username>',
  930. '<password>' + esc(password) + '</password>',
  931. '</login>',
  932. '</se:Body>',
  933. '</se:Envelope>'
  934. ].join('');
  935. var soapLoginEndpoint = [ this.loginUrl, "services/Soap/u", this.version ].join('/');
  936. return this._transport.httpRequest({
  937. method : 'POST',
  938. url : soapLoginEndpoint,
  939. body : body,
  940. headers : {
  941. "Content-Type" : "text/xml",
  942. "SOAPAction" : '""'
  943. }
  944. }).then(function(response) {
  945. var m;
  946. if (response.statusCode >= 400) {
  947. m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
  948. var faultstring = m && m[1];
  949. throw new Error(faultstring || response.body);
  950. }
  951. logger.debug("SOAP response = " + response.body);
  952. m = response.body.match(/<serverUrl>([^<]+)<\/serverUrl>/);
  953. var serverUrl = m && m[1];
  954. m = response.body.match(/<sessionId>([^<]+)<\/sessionId>/);
  955. var sessionId = m && m[1];
  956. m = response.body.match(/<userId>([^<]+)<\/userId>/);
  957. var userId = m && m[1];
  958. m = response.body.match(/<organizationId>([^<]+)<\/organizationId>/);
  959. var orgId = m && m[1];
  960. var idUrl = soapLoginEndpoint.split('/').slice(0, 3).join('/');
  961. idUrl += "/id/" + orgId + "/" + userId;
  962. var userInfo = {
  963. id: userId,
  964. organizationId: orgId,
  965. url: idUrl
  966. };
  967. self.initialize({
  968. serverUrl: serverUrl.split('/').slice(0, 3).join('/'),
  969. sessionId: sessionId,
  970. userInfo: userInfo
  971. });
  972. logger.debug("<login> completed. user id = " + userId + ", org id = " + orgId);
  973. return userInfo;
  974. }).thenCall(callback);
  975. };
  976. /**
  977. * Logout the current session
  978. *
  979. * @param {Callback.<undefined>} [callback] - Callback function
  980. * @returns {Promise.<undefined>}
  981. */
  982. Connection.prototype.logout = function(callback) {
  983. if (this._sessionType === "oauth2") {
  984. return this.logoutByOAuth2(callback);
  985. } else {
  986. return this.logoutBySoap(callback);
  987. }
  988. };
  989. /**
  990. * Logout the current session by revoking access token via OAuth2 session revoke
  991. *
  992. * @param {Callback.<undefined>} [callback] - Callback function
  993. * @returns {Promise.<undefined>}
  994. */
  995. Connection.prototype.logoutByOAuth2 = function(callback) {
  996. var self = this;
  997. var logger = this._logger;
  998. return this.oauth2.revokeToken(this.accessToken).then(function() {
  999. // Destroy the session bound to this connection
  1000. self.accessToken = null;
  1001. self.userInfo = null;
  1002. self.refreshToken = null;
  1003. self.instanceUrl = null;
  1004. self.cache.clear();
  1005. // nothing useful returned by logout API, just return
  1006. return undefined;
  1007. }).thenCall(callback);
  1008. };
  1009. /**
  1010. * Logout the session by using SOAP web service API
  1011. *
  1012. * @param {Callback.<undefined>} [callback] - Callback function
  1013. * @returns {Promise.<undefined>}
  1014. */
  1015. Connection.prototype.logoutBySoap = function(callback) {
  1016. var self = this;
  1017. var logger = this._logger;
  1018. var body = [
  1019. '<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
  1020. '<se:Header>',
  1021. '<SessionHeader xmlns="urn:partner.soap.sforce.com">',
  1022. '<sessionId>' + esc(this.accessToken) + '</sessionId>',
  1023. '</SessionHeader>',
  1024. '</se:Header>',
  1025. '<se:Body>',
  1026. '<logout xmlns="urn:partner.soap.sforce.com"/>',
  1027. '</se:Body>',
  1028. '</se:Envelope>'
  1029. ].join('');
  1030. return this._transport.httpRequest({
  1031. method : 'POST',
  1032. url : [ this.instanceUrl, "services/Soap/u", this.version ].join('/'),
  1033. body : body,
  1034. headers : {
  1035. "Content-Type" : "text/xml",
  1036. "SOAPAction" : '""'
  1037. }
  1038. }).then(function(response) {
  1039. logger.debug("SOAP statusCode = " + response.statusCode + ", response = " + response.body);
  1040. if (response.statusCode >= 400) {
  1041. var m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
  1042. var faultstring = m && m[1];
  1043. throw new Error(faultstring || response.body);
  1044. }
  1045. // Destroy the session bound to this connection
  1046. self.accessToken = null;
  1047. self.userInfo = null;
  1048. self.refreshToken = null;
  1049. self.instanceUrl = null;
  1050. self.cache.clear();
  1051. // nothing useful returned by logout API, just return
  1052. return undefined;
  1053. }).thenCall(callback);
  1054. };
  1055. /**
  1056. * List recently viewed records
  1057. *
  1058. * @param {String} [type] - SObject type
  1059. * @param {Number} [limit] - Limit num to fetch
  1060. * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
  1061. * @returns {Promise.<Array.<RecordResult>>}
  1062. */
  1063. Connection.prototype.recent = function(type, limit, callback) {
  1064. if (!_.isString(type)) {
  1065. callback = limit;
  1066. limit = type;
  1067. type = undefined;
  1068. }
  1069. if (!_.isNumber(limit)) {
  1070. callback = limit;
  1071. limit = undefined;
  1072. }
  1073. var url;
  1074. if (type) {
  1075. url = [ this._baseUrl(), "sobjects", type ].join('/');
  1076. return this.request(url).then(function(res) {
  1077. return limit ? res.recentItems.slice(0, limit) : res.recentItems;
  1078. }).thenCall(callback);
  1079. } else {
  1080. url = this._baseUrl() + "/recent";
  1081. if (limit) {
  1082. url += "?limit=" + limit;
  1083. }
  1084. return this.request(url).thenCall(callback);
  1085. }
  1086. };
  1087. /**
  1088. * @typedef {Object} UpdatedRecordsInfo
  1089. * @prop {String} latestDateCovered - The timestamp of the last date covered.
  1090. * @prop {Array.<String>} ids - Updated record IDs.
  1091. */
  1092. /**
  1093. * Retrieve updated records
  1094. *
  1095. * @param {String} type - SObject Type
  1096. * @param {String|Date} start - start date or string representing the start of the interval
  1097. * @param {String|Date} end - start date or string representing the end of the interval must be > start
  1098. * @param {Callback.<UpdatedRecordsInfo>} [callback] - Callback function
  1099. * @returns {Promise.<UpdatedRecordsInfo>}
  1100. */
  1101. Connection.prototype.updated = function (type, start, end, callback) {
  1102. var url = [ this._baseUrl(), "sobjects", type, "updated" ].join('/');
  1103. if (typeof start === 'string') {
  1104. start = new Date(start);
  1105. }
  1106. if (start instanceof Date) {
  1107. start = formatDate(start);
  1108. }
  1109. if (start) {
  1110. url += "?start=" + encodeURIComponent(start);
  1111. }
  1112. if (typeof end === 'string') {
  1113. end = new Date(end);
  1114. }
  1115. if (end instanceof Date) {
  1116. end = formatDate(end);
  1117. }
  1118. if (end) {
  1119. url += "&end=" + encodeURIComponent(end);
  1120. }
  1121. return this.request(url).thenCall(callback);
  1122. };
  1123. /**
  1124. * @typedef {Object} DeletedRecordsInfo
  1125. * @prop {String} earliestDateAvailable - The timestamp of the earliest date available
  1126. * @prop {String} latestDateCovered - The timestamp of the last date covered
  1127. * @prop {Array.<Object>} deletedRecords - Updated records
  1128. * @prop {String} deletedRecords.id - Record ID
  1129. * @prop {String} deletedRecords.deletedDate - The timestamp when this record was deleted
  1130. */
  1131. /**
  1132. * Retrieve deleted records
  1133. *
  1134. * @param {String} type - SObject Type
  1135. * @param {String|Date} start - start date or string representing the start of the interval
  1136. * @param {String|Date} end - start date or string representing the end of the interval
  1137. * @param {Callback.<DeletedRecordsInfo>} [callback] - Callback function
  1138. * @returns {Promise.<DeletedRecordsInfo>}
  1139. */
  1140. Connection.prototype.deleted = function (type, start, end, callback) {
  1141. var url = [ this._baseUrl(), "sobjects", type, "deleted" ].join('/');
  1142. if (typeof start === 'string') {
  1143. start = new Date(start);
  1144. }
  1145. if (start instanceof Date) {
  1146. start = formatDate(start);
  1147. }
  1148. if (start) {
  1149. url += "?start=" + encodeURIComponent(start);
  1150. }
  1151. if (typeof end === 'string') {
  1152. end = new Date(end);
  1153. }
  1154. if (end instanceof Date) {
  1155. end = formatDate(end);
  1156. }
  1157. if (end) {
  1158. url += "&end=" + encodeURIComponent(end);
  1159. }
  1160. return this.request(url).thenCall(callback);
  1161. };
  1162. /**
  1163. * @typedef {Object} TabsInfo - See the API document for detail structure
  1164. */
  1165. /**
  1166. * Returns a list of all tabs
  1167. *
  1168. * @param {Callback.<TabsInfo>} [callback] - Callback function
  1169. * @returns {Promise.<TabsInfo>}
  1170. */
  1171. Connection.prototype.tabs = function(callback) {
  1172. var url = [ this._baseUrl(), "tabs" ].join('/');
  1173. return this.request(url).thenCall(callback);
  1174. };
  1175. /**
  1176. * @typedef {Object} LimitsInfo - See the API document for detail structure
  1177. */
  1178. /**
  1179. * Returns curren system limit in the organization
  1180. *
  1181. * @param {Callback.<LimitsInfo>} [callback] - Callback function
  1182. * @returns {Promise.<LimitsInfo>}
  1183. */
  1184. Connection.prototype.limits = function(callback) {
  1185. var url = [ this._baseUrl(), "limits" ].join('/');
  1186. return this.request(url).thenCall(callback);
  1187. };
  1188. /**
  1189. * @typedef {Object} ThemeInfo - See the API document for detail structure
  1190. */
  1191. /**
  1192. * Returns a theme info
  1193. *
  1194. * @param {Callback.<ThemeInfo>} [callback] - Callback function
  1195. * @returns {Promise.<ThemeInfo>}
  1196. */
  1197. Connection.prototype.theme = function(callback) {
  1198. var url = [ this._baseUrl(), "theme" ].join('/');
  1199. return this.request(url).thenCall(callback);
  1200. };
  1201. /**
  1202. * Returns all registered global quick actions
  1203. *
  1204. * @param {Callback.<Array.<QuickAction~QuickActionInfo>>} [callback] - Callback function
  1205. * @returns {Promise.<Array.<QuickAction~QuickActionInfo>>}
  1206. */
  1207. Connection.prototype.quickActions = function(callback) {
  1208. return this.request("/quickActions").thenCall(callback);
  1209. };
  1210. /**
  1211. * Get reference for specified global quick aciton
  1212. *
  1213. * @param {String} actionName - Name of the global quick action
  1214. * @returns {QuickAction}
  1215. */
  1216. Connection.prototype.quickAction = function(actionName) {
  1217. return new QuickAction(this, "/quickActions/" + actionName);
  1218. };