Source: cli/cli.js

  1. /*global process */
  2. /**
  3. * @file Command line interface for JSforce
  4. * @author Shinichi Tomita <shinichi.tomita@gmail.com>
  5. */
  6. 'use strict';
  7. var http = require('http'),
  8. url = require('url'),
  9. openUrl = require('open'),
  10. commander = require('commander'),
  11. coprompt = require('co-prompt'),
  12. Repl = require('./repl'),
  13. jsforce = require('../jsforce'),
  14. registry = jsforce.registry,
  15. Promise = require('../promise'),
  16. pkg = require('../../package.json');
  17. var repl;
  18. var conn = null;
  19. var connName = null;
  20. var outputEnabled = true;
  21. var defaultLoginUrl = null;
  22. /**
  23. * @private
  24. */
  25. function start() {
  26. var program = new commander.Command();
  27. program.option('-u, --username [username]', 'Salesforce username')
  28. .option('-p, --password [password]', 'Salesforce password (and security token, if available)')
  29. .option('-c, --connection [connection]', 'Connection name stored in connection registry')
  30. .option('-e, --evalScript [evalScript]', 'Script to evaluate')
  31. .option('-l, --loginUrl [loginUrl]', 'Salesforce login url')
  32. .option('--sandbox', 'Login to Salesforce sandbox')
  33. .option('--coffee', 'Using CoffeeScript')
  34. .version(pkg.version)
  35. .parse(process.argv);
  36. var replModule = program.coffee ? require('coffee-script/lib/coffee-script/repl') : require('repl');
  37. repl = new Repl(cli, replModule);
  38. outputEnabled = !program.evalScript;
  39. var options = { username: program.username, password: program.password };
  40. var loginUrl = program.loginUrl ? program.loginUrl :
  41. program.sandbox ? 'sandbox' :
  42. null;
  43. setLoginServer(loginUrl);
  44. connect(program.connection, options, function(err, res) {
  45. if (err) {
  46. console.error(err.message);
  47. process.exit();
  48. } else {
  49. if (program.evalScript) {
  50. repl.start({
  51. interactive: false,
  52. evalScript: program.evalScript
  53. });
  54. } else {
  55. repl.start();
  56. }
  57. }
  58. });
  59. }
  60. /**
  61. * @private
  62. */
  63. function getCurrentConnection() {
  64. return conn;
  65. }
  66. function print(message) {
  67. if (outputEnabled) { console.log(message); }
  68. }
  69. /**
  70. * @private
  71. */
  72. function saveCurrentConnection() {
  73. if (conn && connName) {
  74. var connConfig = {
  75. oauth2: conn.oauth2 && {
  76. clientId: conn.oauth2.clientId,
  77. clientSecret: conn.oauth2.clientSecret,
  78. redirectUri: conn.oauth2.redirectUri,
  79. loginUrl: conn.oauth2.loginUrl
  80. },
  81. accessToken: conn.accessToken,
  82. instanceUrl: conn.instanceUrl,
  83. refreshToken: conn.refreshToken
  84. };
  85. registry.saveConnectionConfig(connName, connConfig);
  86. }
  87. }
  88. /**
  89. * @private
  90. */
  91. function setLoginServer(loginServer) {
  92. if (!loginServer) { return; }
  93. if (loginServer === 'production') {
  94. defaultLoginUrl = 'https://login.salesforce.com';
  95. } else if (loginServer === 'sandbox') {
  96. defaultLoginUrl = 'https://test.salesforce.com';
  97. } else if (loginServer.indexOf('https://') !== 0) {
  98. defaultLoginUrl = 'https://' + loginServer;
  99. } else {
  100. defaultLoginUrl = loginServer;
  101. }
  102. print('Using "' + defaultLoginUrl + '" as default login URL.');
  103. }
  104. /**
  105. * @private
  106. */
  107. function connect(name, options, callback) {
  108. connName = name;
  109. options = options || {};
  110. var connConfig = registry.getConnectionConfig(name);
  111. var username, password;
  112. if (!connConfig) {
  113. connConfig = {};
  114. if (defaultLoginUrl) {
  115. connConfig.loginUrl = defaultLoginUrl;
  116. }
  117. username = name;
  118. }
  119. conn = new jsforce.Connection(connConfig);
  120. username = username || options.username;
  121. password = options.password;
  122. var handleLogin = function(err) {
  123. if (err) { return callback(err); }
  124. saveCurrentConnection();
  125. callback();
  126. };
  127. if (username) {
  128. loginByPassword(username, password, 2, handleLogin);
  129. } else {
  130. if (connName && conn.accessToken) {
  131. conn.on('refresh', function(accessToken) {
  132. print('Refreshing access token ... ');
  133. saveCurrentConnection();
  134. });
  135. conn.identity(function(err, identity) {
  136. if (err) {
  137. print(err.message);
  138. if (conn.oauth2) {
  139. callback(new Error('Please re-authorize connection.'));
  140. } else {
  141. loginByPassword(connName, null, 2, handleLogin);
  142. }
  143. } else {
  144. print('Logged in as : ' + identity.username);
  145. callback();
  146. }
  147. });
  148. } else {
  149. callback();
  150. }
  151. }
  152. }
  153. /**
  154. * @private
  155. */
  156. function loginByPassword(username, password, retry, callback) {
  157. if (!password) {
  158. promptPassword('Password: ', function(err, pass) {
  159. if (err) { return callback(err); }
  160. loginByPassword(username, pass, retry, callback);
  161. });
  162. return;
  163. }
  164. conn.login(username, password, function(err, result) {
  165. if (err) {
  166. console.error(err.message);
  167. if (retry > 0) {
  168. loginByPassword(username, null, --retry, callback);
  169. } else {
  170. callback(new Error());
  171. }
  172. } else {
  173. print("Logged in as : " + username);
  174. callback(null, result);
  175. }
  176. });
  177. }
  178. /**
  179. * @private
  180. */
  181. function disconnect(name) {
  182. name = name || connName;
  183. if (registry.getConnectionConfig(name)) {
  184. registry.removeConnectionConfig(name);
  185. print("Disconnect connection '" + name + "'");
  186. }
  187. connName = null;
  188. conn = new jsforce.Connection();
  189. }
  190. /**
  191. * @private
  192. */
  193. function authorize(clientName, callback) {
  194. clientName = clientName || 'default';
  195. var oauth2Config = registry.getClient(clientName);
  196. if (!oauth2Config || !oauth2Config.clientId || !oauth2Config.clientSecret) {
  197. return callback(new Error("No OAuth2 client information registered : '"+clientName+"'. Please register client info first."));
  198. }
  199. var oauth2 = new jsforce.OAuth2(oauth2Config);
  200. var state = Math.random().toString(36).substring(2);
  201. var authzUrl = oauth2.getAuthorizationUrl({ state: state });
  202. print('Opening authorization page in browser...');
  203. print('URL: ' + authzUrl);
  204. openUrl(authzUrl);
  205. waitCallback(oauth2Config.redirectUri, state, function(err, params) {
  206. if (err) { return callback(err); }
  207. conn = new jsforce.Connection({ oauth2: oauth2 });
  208. if (!params.code) {
  209. return callback(new Error('No authorization code returned.'));
  210. }
  211. if (params.state !== state) {
  212. return callback(new Error('Invalid state parameter returned.'));
  213. }
  214. print('Received authorization code. Please close the opened browser window.');
  215. conn.authorize(params.code).then(function(res) {
  216. print('Authorized. Fetching user info...');
  217. return conn.identity();
  218. }).then(function(identity) {
  219. print('Logged in as : ' + identity.username);
  220. connName = identity.username;
  221. saveCurrentConnection();
  222. }).thenCall(callback);
  223. });
  224. }
  225. /**
  226. * @private
  227. */
  228. function waitCallback(serverUrl, state, callback) {
  229. if (serverUrl.indexOf('http://localhost:') === 0) {
  230. var server = http.createServer(function(req, res) {
  231. var qparams = url.parse(req.url, true).query;
  232. res.writeHead(200, {'Content-Type': 'text/html'});
  233. res.write('<html><script>location.href="about:blank";</script></html>');
  234. res.end();
  235. callback(null, qparams);
  236. server.close();
  237. req.connection.end();
  238. req.connection.destroy();
  239. });
  240. var port = url.parse(serverUrl).port;
  241. server.listen(port, "localhost");
  242. } else {
  243. var msg = 'Copy & paste authz code passed in redirected URL: ';
  244. promptMessage(msg, function(err, code) {
  245. if (err) {
  246. callback(err);
  247. } else {
  248. callback(null, { code: decodeURIComponent(code), state: state });
  249. }
  250. });
  251. }
  252. }
  253. /**
  254. * @private
  255. */
  256. function register(clientName, clientConfig, callback) {
  257. if (!clientName) {
  258. clientName = "default";
  259. }
  260. var prompts = {
  261. "clientId": "Input client ID (consumer key) : ",
  262. "clientSecret": "Input client secret (consumer secret) : ",
  263. "redirectUri": "Input redirect URI : ",
  264. "loginUrl": "Input login URL (default is https://login.salesforce.com) : "
  265. };
  266. Promise.resolve().then(function() {
  267. var deferred = Promise.defer();
  268. if (registry.getClient(clientName)) {
  269. var msg = "Client '"+clientName+"' is already registered. Are you sure you want to override ? [yN] : ";
  270. promptConfirm(msg, function(err, ok) {
  271. if (ok) {
  272. deferred.resolve();
  273. } else {
  274. deferred.reject(new Error('Registration canceled.'));
  275. }
  276. });
  277. } else {
  278. deferred.resolve();
  279. }
  280. return deferred.promise;
  281. })
  282. .then(function() {
  283. return Object.keys(prompts).reduce(function(promise, name) {
  284. return promise.then(function() {
  285. var message = prompts[name];
  286. var deferred = Promise.defer();
  287. if (!clientConfig[name]) {
  288. promptMessage(message, function(err, value) {
  289. if (err) { return deferred.reject(err); }
  290. if (value) { clientConfig[name] = value; }
  291. deferred.resolve();
  292. });
  293. } else {
  294. deferred.resolve();
  295. }
  296. return deferred.promise;
  297. });
  298. }, Promise.resolve());
  299. }).then(function() {
  300. registry.registerClient(clientName, clientConfig);
  301. print("Client registered successfully.");
  302. }).thenCall(callback);
  303. }
  304. /**
  305. * @private
  306. */
  307. function listConnections() {
  308. var names = registry.getConnectionNames();
  309. for (var i=0; i<names.length; i++) {
  310. var name = names[i];
  311. print((name === connName ? '* ' : ' ') + name);
  312. }
  313. }
  314. /**
  315. * @private
  316. */
  317. function getConnectionNames() {
  318. return registry.getConnectionNames();
  319. }
  320. /**
  321. * @private
  322. */
  323. function getClientNames() {
  324. return registry.getClientNames();
  325. }
  326. /**
  327. * @private
  328. */
  329. function promptMessage(message, callback) {
  330. repl.pause();
  331. coprompt(message)(function(err, res) {
  332. repl.resume();
  333. callback(err, res);
  334. });
  335. }
  336. /**
  337. * @private
  338. */
  339. function promptPassword(message, callback) {
  340. repl.pause();
  341. coprompt.password(message)(function(err, res) {
  342. repl.resume();
  343. callback(err, res);
  344. });
  345. }
  346. /**
  347. * @private
  348. */
  349. function promptConfirm(message, callback) {
  350. repl.pause();
  351. coprompt.confirm(message)(function(err, res) {
  352. repl.resume();
  353. callback(err, res);
  354. });
  355. }
  356. /**
  357. * @private
  358. */
  359. function openUrlUsingSession(url) {
  360. var frontdoorUrl = conn.instanceUrl + '/secur/frontdoor.jsp?sid=' + conn.accessToken;
  361. if (url) {
  362. frontdoorUrl += "&retURL=" + encodeURIComponent(url);
  363. }
  364. openUrl(frontdoorUrl);
  365. }
  366. /* ------------------------------------------------------------------------- */
  367. var cli = {
  368. start: start,
  369. getCurrentConnection: getCurrentConnection,
  370. saveCurrentConnection: saveCurrentConnection,
  371. listConnections: listConnections,
  372. setLoginServer: setLoginServer,
  373. getConnectionNames: getConnectionNames,
  374. getClientNames: getClientNames,
  375. connect: connect,
  376. disconnect: disconnect,
  377. authorize: authorize,
  378. register: register,
  379. openUrl: openUrlUsingSession
  380. };
  381. module.exports = cli;