UNPKG

mailchimp

Version:

A node.js wrapper for the MailChimp API.

315 lines (267 loc) 11 kB
var http = require('http'), https = require('http'), util = require('util'), url = require('url'), querystring = require('querystring'), EventEmitter = require('events').EventEmitter, fs = require('fs'), request = require('request'); /** * Provides functionality for OAuth2 authorization with MailChimp. A server is * set up that listens for requests from MailChimp that are made upon * successful user authentication. Using this server the whole workflow * necessary to authorize against MailChimp is made available. * * The workflow using this module in short is as follows. An example can be * found in the README. * * 1) Instatiate MailChimpOAuth. * 2) Send the user to the MailChimp login form using the url returned from * MailChimpOAuth.getAuthorizeUri(); * 3) Listen for the 'authed' event and pass MailChimpOAuth.api as th API key * to whatever MailChimp API methods you want to use. * * Details on the process and further information on how to set everything up * are available in the MailChimp API docs. * * @see http://apidocs.mailchimp.com/oauth2/ * * Available options are: * `clientId` The clientId can be obtained from MailChimp, please refer to the API docs on how to do this. The clientId is a required parameter. * `clientSecret` The clientSecret can be obtained from MailChimp, please refer to the API docs on how to do this. The clientSecret is a required parameter. * `redirectUri` Redirect URI from MailChimp App Configuration * `ownServer` Boolean to enable own custom server for listening to redirect URI. Defaults to false. * `addPort` Boolean to add value of port number in redirectUri defaults to false. * `port` The port the server is going to listen on. Defaults to 8100. These fields are not needed if ownServer is false * `finalUri` After a successful authorization on the MailChimp website the user is redirected to this URI, if any. * `secure` Credentials in the form {key:path to ssl key file, cert: path to ssl certificate file} . If present HTTPS support is enabled for the server. Defaults to false. You can register the following events: * `error` This event is emitted when an error occured and receives one argument that contains the error message. * `authed` Emitted when the OAuth was completed successfully. Receives one argument which represents the API key that can be passed on to other API functionality. * * @param options Configuration options * @return Nothing. Emits an 'authed' event instead which indicates everything * went fine */ function MailChimpOAuth(options) { var self = this; options = options || {}; EventEmitter.call(this); this.httpPort = options.port || 8100; this.spawnServerFlag = options.ownServer || false; this.secure = options.secure || false; this.finalUri = options.finalUri || false; this.addPort = options.addPort || false; if (typeof options.clientId !== 'undefined') { this.clientId = options.clientId; } else { throw new Error('You have to specify the client id for this to work.'); } if (typeof options.clientSecret !== 'undefined') { this.clientSecret = options.clientSecret; } else { throw new Error('You have to specify the client secret for this to work.'); } if (typeof options.redirectUri !== 'undefined') { this.redirectUri = options.redirectUri; } else { throw new Error('You have to specify a uri for this server as MailChimp needs to reach it from the outside.'); } try { var packageInfo = fs.readFileSync(__dirname + "/../../package.json"); } catch (error) { throw new Error('Required package file package.json not found for this module.'); } this.packageInfo = JSON.parse(packageInfo.toString()); this.on('receivedCode', function (customParams) { self.getAccessToken(customParams); }); this.on('receivedAccessToken', function (customParams) { self.getMetadata(customParams); }); this.on('receivedMetadata', function (customParams) { self.emit('authed', customParams); }); if (this.spawnServerFlag) this.spawnServer(); } util.inherits(MailChimpOAuth, EventEmitter); module.exports = MailChimpOAuth; /** * Spawns a server that listens for incoming GET requests. The url to the * server is passed on to MailChimp as a get parameter to the authorization * form. When a request is received and valid the chain of authorization is set * in motion. This method should not be called directly but will be used * internally when needed. * * @return Nothing. Emits either an 'error' or a 'receivedCode' event */ MailChimpOAuth.prototype.spawnServer = function () { var self = this; var callback = function (request, response) { if (request.method != 'GET') { self.emit('error', {err: 'Received something other than a GET request.', data:undefined}); response.writeHead(500, {'Content-Type': 'text/plain'}); response.end(); return; } var requestUrl = url.parse(request.url); var query = querystring.parse(requestUrl.query) || {}; if (typeof query.code !== 'undefined') { self.emit('receivedCode', query); if (self.finalUri) { response.writeHead(302, {'Location': self.finalUri}); response.end(); } else { response.writeHead(204, {'Content-Type': 'text/plain'}); response.end(); } } else { self.emit('error', {err: 'Received a request without a code.', data:query}); response.writeHead(500, {'Content-Type': 'text/plain'}); response.end(); } }; var server; if (this.secure) { if (this.secure.key && this.secure.cert) { const options = { key: fs.readFileSync(this.secure.key), cert: fs.readFileSync(this.secure.cert) }; server = https.createServer(options, callback); } else { throw new Error('You have to specify the complete ssl credentials for this to work.with key and code'); } } else server = http.createServer(callback); server.listen(this.httpPort); } /** * handle response from * @return Nothing. Emits either an 'error' or a 'receivedCode' event */ MailChimpOAuth.prototype.handleOAuthResponse = function (customParams) { var self = this; customParams = customParams || {}; if (typeof customParams.code !== 'undefined') { self.emit('receivedCode', customParams); } else { self.emit('error', {err: 'Received a request without a code.', data:customParams}); } }; /** * Assembles and returns a URI the user needs to be send to for authorization, * on that page the MailChimp login form is shown and the user prompted for his * username and password. The URI includes a link to the server spawned while * instantiating MailChimpOAuth. * * @return URI to the autorization page */ MailChimpOAuth.prototype.getAuthorizeUri = function () { var params = { response_type: 'code', client_id: this.clientId, redirect_uri: this.addPort ? this.redirectUri + ':' + this.httpPort : this.redirectUri }; return 'https://login.mailchimp.com/oauth2/authorize?' + querystring.stringify(params); } /** * Get the access token from MailChimp once the code for a successful * authorization was received. When the access token was successfully received * an event is emitted which MailChimpOAuth is listening on to request the * OAuth metadata. This method should not be called directly but will be used * internally when needed. * * @return Nothing. Emits either an 'error' or a 'receivedAccessToken' event */ MailChimpOAuth.prototype.getAccessToken = function (customParams) { var self = this; customParams = customParams || {}; if (!customParams.code) { self.emit('error', {err: 'Code is required in Params', data:customParams}); return; } var params = { grant_type: 'authorization_code', client_id: this.clientId, client_secret: this.clientSecret, code: customParams.code, redirect_uri: this.addPort ? this.redirectUri + ':' + this.httpPort : this.redirectUri }; request({ uri: 'https://login.mailchimp.com/oauth2/token', method: 'POST', headers: { 'User-Agent': 'node-mailchimp/' + this.packageInfo['version'], 'Content-Type': 'application/x-www-form-urlencoded' }, body: querystring.stringify(params) }, function (error, response, body) { var parsedResponse; if (error) { self.emit('error', {err: 'Unable to connect to the MailChimp OAuth service.', data:customParams}); } else { try { parsedResponse = JSON.parse(body); } catch (error) { console.log('[MailChimp] Error parsing JSON answer from the MailChimp getAccessToken API.', body); self.emit('error', {err: 'Error parsing JSON answer from the MailChimp getAccessToken API.', data:customParams}); return; } if (typeof parsedResponse.access_token !== 'undefined') { customParams.accessToken = parsedResponse.access_token; self.emit('receivedAccessToken', customParams); } else { self.emit('error', {err: 'Answer from MailChimp API does not contain an access token.', data:customParams}); } } }); } /** * Get metadata from MailChimp once the access token was received. When the * metadata was successfully received an event is emitted which MailChimpOAuth * is listening on to finalize the authentication process. This method should * not be called directly but will be used internally when needed. * * @return Nothing. Emits either an 'error' or a 'receivedMetadata' event */ MailChimpOAuth.prototype.getMetadata = function (customParams) { var self = this; customParams = customParams || {}; if (!customParams.accessToken) { self.emit('error', {err: 'accessToken is required in Params', data:customParams}); return; } request({ uri: 'https://login.mailchimp.com/oauth2/metadata', headers: { 'User-Agent': 'node-mailchimp/' + this.packageInfo['version'], 'Authorization': 'OAuth ' + customParams.accessToken } }, function (error, response, body) { var parsedResponse; if (error) { self.emit('error', {err: 'Unable to connect to the MailChimp OAuth service.', data:customParams}); return; } else { try { parsedResponse = JSON.parse(body); } catch (error) { console.error('[MailChimp] Error parsing JSON answer from the MailChimp getMetadata API.', body); self.emit('error', {err: 'Error parsing JSON answer from the MailChimp getMetadata API.', data:customParams}); return; } if (typeof parsedResponse.dc !== 'undefined') { customParams.metadata = parsedResponse; customParams.apiKey = customParams.accessToken + '-' + customParams.metadata.dc; self.emit('receivedMetadata', customParams); } else { self.emit('error', {err: 'Answer from MailChimp API does not contain a datacenter pointer.', data:customParams}); } } }); }