jsforce2
Version:
Salesforce API Library for JavaScript
338 lines (304 loc) • 8.56 kB
JavaScript
/*global process, global */
/**
* @file Creates REPL interface with built in Salesforce API objects and automatically resolves promise object
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
* @private
*/
;
var stream = require('readable-stream'),
Readable = stream.Readable,
Writable = stream.Writable,
Transform = stream.Transform,
_ = require('lodash/core'),
jsforce = require('../jsforce');
/**
* Intercept the evaled value returned from repl evaluator, convert and send back to output.
* @private
*/
function injectBefore(replServer, method, beforeFn) {
var _orig = replServer[method];
replServer[method] = function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
beforeFn.apply(null, args.concat(function(err, res) {
if (err || res) {
callback(err, res);
} else {
_orig.apply(replServer, args.concat(callback));
}
}));
};
return replServer;
}
/**
* @private
*/
function injectAfter(replServer, method, afterFn) {
var _orig = replServer[method];
replServer[method] = function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
_orig.apply(replServer, args.concat(function() {
var args = Array.prototype.slice.call(arguments);
try {
afterFn.apply(null, args.concat(callback));
} catch(e) {
callback(e);
}
}));
};
return replServer;
}
/**
* When the result was "promise", resolve its value
* @private
*/
function promisify(err, value, callback) {
if (err) { throw err; }
if (isPromiseLike(value)) {
value.then(function(v) {
callback(null, v);
}, function(err) {
callback(err);
});
} else {
callback(null, value);
}
}
/**
* Detect whether the value has CommonJS Promise/A+ interface or not
* @private
*/
function isPromiseLike(v) {
return _.isObject(v) && _.isFunction(v.then);
}
/**
* Output object to stdout in JSON representation
* @private
*/
function outputToStdout(prettyPrint) {
if (prettyPrint && !_.isNumber(prettyPrint)) {
prettyPrint = 4;
}
return function(err, value, callback) {
if (err) {
console.error(err);
} else {
var str = JSON.stringify(value, null, prettyPrint);
console.log(str);
}
callback(err, value);
};
}
/**
* define get accessor using Object.defineProperty
* @private
*/
function defineProp(obj, prop, getter) {
if (Object.defineProperty) {
Object.defineProperty(obj, prop, { get: getter });
}
}
/**
* @private
*/
var Repl = module.exports = function(cli, replModule) {
this._cli = cli;
this._replModule = replModule;
this._in = new Transform();
this._out = new Transform();
var self = this;
this._in._transform = function(chunk, encoding, callback) {
if (!self._paused) { this.push(chunk); }
callback();
};
this._out._transform = function(chunk, encoding, callback) {
if (!self._paused && self._interactive !== false) { this.push(chunk); }
callback();
};
};
/**
*
*/
Repl.prototype.start = function(options) {
var self = this;
var cli = this._cli;
options = options || {};
process.stdin.resume();
process.stdin.setRawMode(true);
process.stdin.pipe(this._in);
this._interactive = options.interactive;
this._out.pipe(process.stdout);
defineProp(this._out, "columns", function() { return process.stdout.columns; });
var replServer = this._replModule.start({
input: this._in,
output: this._out,
terminal: true
});
this._defineAdditionalCommands(replServer);
replServer = injectBefore(replServer, "completer", function() {
self.complete.apply(self, arguments);
});
replServer = injectAfter(replServer, "eval", promisify);
if (options.interactive === false) {
replServer = injectAfter(replServer, "eval", outputToStdout(options.prettyPrint));
replServer = injectAfter(replServer, "eval", function() { process.exit(); });
}
replServer.on('exit', function() { process.exit(); });
this._defineBuiltinVars(replServer.context);
if (options.evalScript) {
this._in.write(options.evalScript + "\n", "utf-8");
}
return this;
};
/**
* @private
*/
Repl.prototype._defineAdditionalCommands = function(replServer) {
var cli = this._cli;
replServer.defineCommand('connections', {
help: 'List currenty registered Salesforce connections',
action: function(name) {
cli.listConnections();
replServer.displayPrompt();
}
});
replServer.defineCommand('connect', {
help: 'Connect to Salesforce instance',
action: function(name, password) {
var options = null;
if (password) {
options = { username: name, password: password };
}
cli.connect(name, options, function(err, res) {
if (err) { console.error(err.message); }
replServer.displayPrompt();
});
}
});
replServer.defineCommand('disconnect', {
help: 'Disconnect connection and erase it from registry',
action: function(name) {
cli.disconnect(name);
replServer.displayPrompt();
}
});
replServer.defineCommand('use', {
help: 'Specify login server to establish connection',
action: function(loginServer) {
cli.setLoginServer(loginServer);
replServer.displayPrompt();
}
});
replServer.defineCommand('authorize', {
help: 'Connect to Salesforce using OAuth2 authorization flow',
action: function(clientName) {
cli.authorize(clientName, function(err, res) {
if (err) { console.error(err.message); }
replServer.displayPrompt();
});
}
});
replServer.defineCommand('register', {
help: 'Register OAuth2 client information',
action: function(clientName, clientId, clientSecret, redirectUri, loginUrl) {
var config = {
clientId: clientId,
clientSecret: clientSecret,
redirectUri: redirectUri,
loginUrl: loginUrl
};
cli.register(clientName, config, function(err, res) {
if (err) { console.error(err.message); }
replServer.displayPrompt();
});
}
});
replServer.defineCommand('open', {
help: 'Open Salesforce web page using established connection',
action: function(url) {
cli.openUrl(url);
replServer.displayPrompt();
}
});
};
/**
*
*/
Repl.prototype.pause = function() {
this._paused = true;
process.stdin.setRawMode(false);
};
/**
*
*/
Repl.prototype.resume = function() {
this._paused = false;
process.stdin.resume();
process.stdin.setRawMode(true);
};
/**
*
*/
Repl.prototype.complete = function(line, callback) {
var tokens = line.replace(/^\s+/, '').split(/\s+/);
var command = tokens[0];
var keyword = tokens[1] || '';
if (command[0] === '.' && tokens.length === 2) {
var candidates = [];
if (command === '.connect' || command === '.disconnect') {
candidates = this._cli.getConnectionNames();
} else if (command === '.authorize') {
candidates = this._cli.getClientNames();
} else if (command === '.use') {
candidates = [ 'production', 'sandbox' ];
}
candidates = candidates.filter(function(name) {
return name.indexOf(keyword) === 0;
});
callback(null, [ candidates, keyword ]);
} else {
callback();
}
};
/**
* Map all node-salesforce object to REPL context
* @private
*/
Repl.prototype._defineBuiltinVars = function(context) {
var cli = this._cli;
// define salesforce package root objects
for (var key in jsforce) {
if (jsforce.hasOwnProperty(key) && !global[key]) {
context[key] = jsforce[key];
}
}
// expose jsforce package root object in context.
context.jsforce = jsforce;
function createProxyFunc(prop) {
return function() {
var conn = cli.getCurrentConnection();
return conn[prop].apply(conn, arguments);
};
}
function createProxyAccessor(prop) {
return function() {
var conn = cli.getCurrentConnection();
return conn[prop];
};
}
var conn = cli.getCurrentConnection();
// define connection prototype functions as proxy
for (var prop in conn) {
if (prop.indexOf('_') === 0) { // ignore private
continue;
}
if (_.isFunction(conn[prop])) {
context[prop] = createProxyFunc(prop);
} else if (_.isObject(conn[prop])) {
defineProp(context, prop, createProxyAccessor(prop));
}
}
// expose default connection as "$conn"
defineProp(context, "$conn", function(){ return cli.getCurrentConnection(); });
};