UNPKG

jsforce2

Version:

Salesforce API Library for JavaScript

432 lines (404 loc) 11.8 kB
/*global process */ /** * @file Command line interface for JSforce * @author Shinichi Tomita <shinichi.tomita@gmail.com> */ 'use strict'; var http = require('http'), url = require('url'), crypto = require('crypto'), openUrl = require('opn'), commander = require('commander'), coprompt = require('co-prompt'), request = require('request'), base64url = require('base64-url'), Repl = require('./repl'), jsforce = require('../jsforce'), registry = jsforce.registry, Promise = require('../promise'), pkg = require('../../package.json'); var repl; var conn = null; var connName = null; var outputEnabled = true; var defaultLoginUrl = null; /** * @private */ function start() { var program = new commander.Command(); program.option('-u, --username [username]', 'Salesforce username') .option('-p, --password [password]', 'Salesforce password (and security token, if available)') .option('-c, --connection [connection]', 'Connection name stored in connection registry') .option('-e, --evalScript [evalScript]', 'Script to evaluate') .option('-l, --loginUrl [loginUrl]', 'Salesforce login url') .option('--sandbox', 'Login to Salesforce sandbox') .option('--coffee', 'Using CoffeeScript') .version(pkg.version) .parse(process.argv); var replModule = program.coffee ? require('coffee-script/lib/coffee-script/repl') : require('repl'); repl = new Repl(cli, replModule); outputEnabled = !program.evalScript; var options = { username: program.username, password: program.password }; var loginUrl = program.loginUrl ? program.loginUrl : program.sandbox ? 'sandbox' : null; setLoginServer(loginUrl); connect(program.connection, options, function(err, res) { if (err) { console.error(err.message); process.exit(); } else { if (program.evalScript) { repl.start({ interactive: false, evalScript: program.evalScript }); } else { repl.start(); } } }); } /** * @private */ function getCurrentConnection() { return conn; } function print(message) { if (outputEnabled) { console.log(message); } } /** * @private */ function saveCurrentConnection() { if (conn && connName) { var connConfig = { oauth2: conn.oauth2 && { clientId: conn.oauth2.clientId, clientSecret: conn.oauth2.clientSecret, redirectUri: conn.oauth2.redirectUri, loginUrl: conn.oauth2.loginUrl }, accessToken: conn.accessToken, instanceUrl: conn.instanceUrl, refreshToken: conn.refreshToken }; registry.saveConnectionConfig(connName, connConfig); } } /** * @private */ function setLoginServer(loginServer) { if (!loginServer) { return; } if (loginServer === 'production') { defaultLoginUrl = 'https://login.salesforce.com'; } else if (loginServer === 'sandbox') { defaultLoginUrl = 'https://test.salesforce.com'; } else if (loginServer.indexOf('https://') !== 0) { defaultLoginUrl = 'https://' + loginServer; } else { defaultLoginUrl = loginServer; } print('Using "' + defaultLoginUrl + '" as default login URL.'); } /** * @private */ function connect(name, options, callback) { connName = name; options = options || {}; var connConfig = registry.getConnectionConfig(name); var username, password; if (!connConfig) { connConfig = {}; if (defaultLoginUrl) { connConfig.loginUrl = defaultLoginUrl; } username = name; } conn = new jsforce.Connection(connConfig); username = username || options.username; password = options.password; var handleLogin = function(err) { if (err) { return callback(err); } saveCurrentConnection(); callback(); }; if (username) { loginByPassword(username, password, 2, handleLogin); } else { if (connName && conn.accessToken) { conn.on('refresh', function(accessToken) { print('Refreshing access token ... '); saveCurrentConnection(); }); conn.identity(function(err, identity) { if (err) { print(err.message); if (conn.oauth2) { callback(new Error('Please re-authorize connection.')); } else { loginByPassword(connName, null, 2, handleLogin); } } else { print('Logged in as : ' + identity.username); callback(); } }); } else { callback(); } } } /** * @private */ function loginByPassword(username, password, retry, callback) { if (!password) { promptPassword('Password: ', function(err, pass) { if (err) { return callback(err); } loginByPassword(username, pass, retry, callback); }); return; } conn.login(username, password, function(err, result) { if (err) { console.error(err.message); if (retry > 0) { loginByPassword(username, null, --retry, callback); } else { callback(new Error()); } } else { print("Logged in as : " + username); callback(null, result); } }); } /** * @private */ function disconnect(name) { name = name || connName; if (registry.getConnectionConfig(name)) { registry.removeConnectionConfig(name); print("Disconnect connection '" + name + "'"); } connName = null; conn = new jsforce.Connection(); } /** * @private */ function authorize(clientName, callback) { clientName = clientName || 'default'; var oauth2Config = registry.getClient(clientName); if (!oauth2Config || !oauth2Config.clientId) { if (clientName === 'default' || clientName === 'sandbox') { print('No client information registered. Downloading JSforce default client information...'); return downloadDefaultClientInfo(clientName, callback); } return callback(new Error("No OAuth2 client information registered : '"+clientName+"'. Please register client info first.")); } var oauth2 = new jsforce.OAuth2(oauth2Config); var verifier = base64url.encode(crypto.randomBytes(32)); var challenge = base64url.encode(crypto.createHash('sha256').update(verifier).digest()); var state = base64url.encode(crypto.randomBytes(32)); var authzUrl = oauth2.getAuthorizationUrl({ code_challenge: challenge, state: state }); print('Opening authorization page in browser...'); print('URL: ' + authzUrl); openUrl(authzUrl); waitCallback(oauth2Config.redirectUri, state, function(err, params) { if (err) { return callback(err); } conn = new jsforce.Connection({ oauth2: oauth2 }); if (!params.code) { return callback(new Error('No authorization code returned.')); } if (params.state !== state) { return callback(new Error('Invalid state parameter returned.')); } print('Received authorization code. Please close the opened browser window.'); conn.authorize(params.code, { code_verifier: verifier }).then(function(res) { print('Authorized. Fetching user info...'); return conn.identity(); }).then(function(identity) { print('Logged in as : ' + identity.username); connName = identity.username; saveCurrentConnection(); }).thenCall(callback); }); } /** * @private */ function downloadDefaultClientInfo(clientName, callback) { var configUrl = 'https://jsforce.github.io/client-config/default.json'; request(configUrl, function(err, res) { if (err) { return callback(err); } var clientConfig = JSON.parse(res.body); if (clientName === 'sandbox') { clientConfig.loginUrl = 'https://test.salesforce.com'; } registry.registerClient(clientName, clientConfig); print("Client information downloaded successfully."); authorize(clientName, callback); }); } /** * @private */ function waitCallback(serverUrl, state, callback) { if (serverUrl.indexOf('http://localhost:') === 0) { var server = http.createServer(function(req, res) { var qparams = url.parse(req.url, true).query; res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<html><script>location.href="about:blank";</script></html>'); res.end(); callback(null, qparams); server.close(); req.connection.end(); req.connection.destroy(); }); var port = url.parse(serverUrl).port; server.listen(port, "localhost"); } else { var msg = 'Copy & paste authz code passed in redirected URL: '; promptMessage(msg, function(err, code) { if (err) { callback(err); } else { callback(null, { code: decodeURIComponent(code), state: state }); } }); } } /** * @private */ function register(clientName, clientConfig, callback) { if (!clientName) { clientName = "default"; } var prompts = { "clientId": "Input client ID : ", "clientSecret": "Input client secret (optional) : ", "redirectUri": "Input redirect URI : ", "loginUrl": "Input login URL (default is https://login.salesforce.com) : " }; new Promise(function(resolve, reject) { if (registry.getClient(clientName)) { var msg = "Client '"+clientName+"' is already registered. Are you sure you want to override ? [yN] : "; promptConfirm(msg, function(err, ok) { if (ok) { resolve(); } else { reject(new Error('Registration canceled.')); } }); } else { resolve(); } }) .then(function() { return Object.keys(prompts).reduce(function(promise, name) { return promise.then(function() { var message = prompts[name]; return new Promise(function(resolve, reject) { if (!clientConfig[name]) { promptMessage(message, function(err, value) { if (err) { return reject(err); } if (value) { clientConfig[name] = value; } resolve(); }); } else { resolve(); } }); }); }, Promise.resolve()); }).then(function() { registry.registerClient(clientName, clientConfig); print("Client registered successfully."); }).thenCall(callback); } /** * @private */ function listConnections() { var names = registry.getConnectionNames(); for (var i=0; i<names.length; i++) { var name = names[i]; print((name === connName ? '* ' : ' ') + name); } } /** * @private */ function getConnectionNames() { return registry.getConnectionNames(); } /** * @private */ function getClientNames() { return registry.getClientNames(); } /** * @private */ function promptMessage(message, callback) { repl.pause(); coprompt(message)(function(err, res) { repl.resume(); callback(err, res); }); } /** * @private */ function promptPassword(message, callback) { repl.pause(); coprompt.password(message)(function(err, res) { repl.resume(); callback(err, res); }); } /** * @private */ function promptConfirm(message, callback) { repl.pause(); coprompt.confirm(message)(function(err, res) { repl.resume(); callback(err, res); }); } /** * @private */ function openUrlUsingSession(url) { var frontdoorUrl = conn.instanceUrl + '/secur/frontdoor.jsp?sid=' + conn.accessToken; if (url) { frontdoorUrl += "&retURL=" + encodeURIComponent(url); } openUrl(frontdoorUrl); } /* ------------------------------------------------------------------------- */ var cli = { start: start, getCurrentConnection: getCurrentConnection, saveCurrentConnection: saveCurrentConnection, listConnections: listConnections, setLoginServer: setLoginServer, getConnectionNames: getConnectionNames, getClientNames: getClientNames, connect: connect, disconnect: disconnect, authorize: authorize, register: register, openUrl: openUrlUsingSession }; module.exports = cli;