/**
* @file Manages Salesforce Chatter REST API calls
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
'use strict';
var inherits = require('inherits'),
_ = require('lodash/core'),
jsforce = require('../core'),
Promise = require('../promise');
/**
* API class for Chatter REST API call
*
* @class
* @param {Connection} conn Connection
*/
var Chatter = module.exports = function(conn) {
this._conn = conn;
};
/**
* Sending request to API endpoint
* @private
*/
Chatter.prototype._request = function(params, callback) {
if (/^(put|post|patch)$/i.test(params.method)) {
if (_.isObject(params.body)) {
params.headers = {
"Content-Type": "application/json"
};
params.body = JSON.stringify(params.body);
}
}
params.url = this._normalizeUrl(params.url);
return this._conn.request(params, callback);
};
/**
* Convert path to site root relative url
* @private
*/
Chatter.prototype._normalizeUrl = function(url) {
if (url.indexOf('/chatter/') === 0 || url.indexOf('/connect/') === 0) {
return '/services/data/v' + this._conn.version + url;
} else if (/^\/v[\d]+\.[\d]+\//.test(url)) {
return '/services/data' + url;
} else if (url.indexOf('/services/') !== 0 && url[0] === '/') {
return '/services/data/v' + this._conn.version + '/chatter' + url;
} else {
return url;
}
};
/**
* @typedef {Object} Chatter~RequestParams
* @prop {String} method - HTTP method
* @prop {String} url - Resource URL
* @prop {String} [body] - HTTP body (in POST/PUT/PATCH methods)
*/
/**
* @typedef {Object} Chatter~RequestResult
*/
/**
* Make a request for chatter API resource
*
* @param {Chatter~RequestParams} params - Paramters representing HTTP request
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback func
* @returns {Chatter~Request}
*/
Chatter.prototype.request = function(params, callback) {
return new Request(this, params).thenCall(callback);
};
/**
* Make a resource request to chatter API
*
* @param {String} url - Resource URL
* @param {Object} [queryParams] - Query parameters (in hash object)
* @returns {Chatter~Resource}
*/
Chatter.prototype.resource = function(url, queryParams) {
return new Resource(this, url, queryParams);
};
/**
* @typedef {Object} Chatter~BatchRequestResult
* @prop {Boolean} hasError - Flag if the batch has one or more errors
* @prop {Array.<Object>} results - Batch request results in array
* @prop {Number} results.statusCode - HTTP response status code
* @prop {Chatter~RequestResult} results.result - Parsed HTTP response body
*/
/**
* Make a batch request to chatter API
*
* @params {Array.<Chatter~Request>} requests - Chatter API requests
* @param {Callback.<Chatter~BatchRequestResult>} [callback] - Callback func
* @returns {Promise.<Chatter~BatchRequestResult>}
*/
Chatter.prototype.batch = function(requests, callback) {
var self = this;
var batchRequests = [], batchDeferreds = [];
_.forEach(requests, function(request) {
var deferred = Promise.defer();
request._promise = deferred.promise;
batchRequests.push(request.batchParams());
batchDeferreds.push(deferred);
});
var params = {
method: 'POST',
url: this._normalizeUrl('/connect/batch'),
body: {
batchRequests: batchRequests
}
};
return this.request(params).then(function(res) {
_.forEach(res.results, function(result, i) {
var deferred = batchDeferreds[i];
if (result.statusCode >= 400) {
deferred.reject(result.result);
} else {
deferred.resolve(result.result);
}
});
return res;
}).thenCall(callback);
};
/*--------------------------------------------*/
/**
* A class representing chatter API request
*
* @protected
* @class Chatter~Request
* @implements {Promise.<Chatter~RequestResult>}
* @param {Chatter} chatter - Chatter API object
* @param {Chatter~RequestParams} params - Paramters representing HTTP request
*/
var Request = function(chatter, params) {
this._chatter = chatter;
this._params = params;
this._promise = null;
};
/**
* @typedef {Object} Chatter~BatchRequestParams
* @prop {String} method - HTTP method
* @prop {String} url - Resource URL
* @prop {String} [richInput] - HTTP body (in POST/PUT/PATCH methods)
*/
/**
* Retrieve parameters in batch request form
*
* @method Chatter~Request#batchParams
* @returns {Chatter~BatchRequestParams}
*/
Request.prototype.batchParams = function() {
var params = this._params;
var batchParams = {
method: params.method,
url: this._chatter._normalizeUrl(params.url)
};
if (this._params.body) {
batchParams.richInput = this._params.body;
}
return batchParams;
};
/**
* Retrieve parameters in batch request form
*
* @method Chatter~Request#promise
* @returns {Promise.<Chatter~RequestResult>}
*/
Request.prototype.promise = function() {
return this._promise || this._chatter._request(this._params);
};
/**
* Returns Node.js Stream object for request
*
* @method Chatter~Request#stream
* @returns {stream.Stream}
*/
Request.prototype.stream = function() {
return this._chatter._request(this._params).stream();
};
/**
* Promise/A+ interface
* http://promises-aplus.github.io/promises-spec/
*
* Delegate to deferred promise, return promise instance for batch result
*
* @method Chatter~Request#then
*/
Request.prototype.then = function(onResolve, onReject) {
return this.promise().then(onResolve, onReject);
};
/**
* Promise/A+ extension
* Call "then" using given node-style callback function
*
* @method Chatter~Request#thenCall
*/
Request.prototype.thenCall = function(callback) {
return _.isFunction(callback) ? this.promise().thenCall(callback) : this;
};
/*--------------------------------------------*/
/**
* A class representing chatter API resource
*
* @protected
* @class Chatter~Resource
* @extends Chatter~Request
* @param {Chatter} chatter - Chatter API object
* @param {String} url - Resource URL
* @param {Object} [queryParams] - Query parameters (in hash object)
*/
var Resource = function(chatter, url, queryParams) {
if (queryParams) {
var qstring = _.map(_.keys(queryParams), function(name) {
return name + "=" + encodeURIComponent(queryParams[name]);
}).join('&');
url += (url.indexOf('?') > 0 ? '&' : '?') + qstring;
}
Resource.super_.call(this, chatter, { method: 'GET', url: url });
this._url = url;
};
inherits(Resource, Request);
/**
* Create a new resource
*
* @method Chatter~Resource#create
* @param {Object} data - Data to newly post
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.create = function(data, callback) {
return this._chatter.request({
method: 'POST',
url: this._url,
body: data
}).thenCall(callback);
};
/**
* Retrieve resource content
*
* @method Chatter~Resource#retrieve
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.retrieve = function(callback) {
return this.thenCall(callback);
};
/**
* Update specified resource
*
* @method Chatter~Resource#update
* @param {Obejct} data - Data to update
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.update = function(data, callback) {
return this._chatter.request({
method: 'POST',
url: this._url,
body: data
}).thenCall(callback);
};
/**
* Synonym of Resource#delete()
*
* @method Chatter~Resource#del
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
/**
* Delete specified resource
*
* @method Chatter~Resource#delete
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.del =
Resource.prototype["delete"] = function(callback) {
return this._chatter.request({
method: 'DELETE',
url: this._url
}).thenCall(callback);
};
/*--------------------------------------------*/
/*
* Register hook in connection instantiation for dynamically adding this API module features
*/
jsforce.on('connection:new', function(conn) {
conn.chatter = new Chatter(conn);
});