UNPKG

10.1 kBJavaScriptView Raw
1/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2015 Mailjet
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 *
24 */
25
26const DEBUG_MODE = false
27const RESOURCE = 0
28const ID = 1
29const ACTION = 2
30
31const STRICT = false
32
33/*
34 * Imports.
35 *
36 * qs is used to format the url from the provided parameters and method
37 * _path will join a path according to the OS specifications
38 * https will be used to make a secure http request to the API
39 * fs will simply be used to read files
40 */
41
42const qs = require('querystring')
43const request = require('superagent')
44const Promise = require('bluebird')
45const _path = require('path')
46const JSONb = require('json-bigint')({ storeAsString: true })
47const version = require('./package.json').version
48
49/*
50 * MailjetClient constructor.
51 *
52 * @qpi_key (optional) {String} mailjet account api key
53 * @api_secret (optional) {String} mailjet account api secret
54 *
55 * If you don't know what this is about, sign up to Mailjet at:
56 * https://www.mailjet.com/
57 */
58function MailjetClient (api_key, api_secret, testMode) {
59 this.config = require('./config')
60 this.testMode = testMode || false
61 // To be updated according to the npm repo version
62 this.version = version
63 if (api_key && api_secret) {
64 this.connect(api_key, api_secret)
65 }
66}
67
68MailjetClient.prototype.typeJson = function (body) {
69 var keys = Object.keys(body)
70 for (var i in keys) {
71 var key = keys[i]
72 body[key] = parseInt(body[key]) || body[key]
73 }
74 return body
75}
76
77/*
78 * [Static] connect.
79 *
80 * Return a nez connected instance of the MailjetClient class
81 *
82 * @k {String} mailjet qpi key
83 * @s {String} mailjet api secret
84 *
85 */
86MailjetClient.connect = function (k, s) {
87 return new MailjetClient().connect(k, s)
88}
89
90/*
91 * connect.
92 *
93 * create a auth property from the api key and secret
94 *
95 * @api_key {String}
96 * @api_secret {String}
97 *
98 */
99MailjetClient.prototype.connect = function (apiKey, apiSecret) {
100 this.apiKey = apiKey
101 this.apiSecret = apiSecret
102 return this
103}
104
105/*
106 * path.
107 *
108 * Returns a formatted url from a http method and
109 * a parameters object literal
110 *
111 * @resource {String}
112 * @sub {String} REST/''/DATA
113 * @params {Object literal} {name: value}
114 *
115 */
116MailjetClient.prototype.path = function (resource, sub, params) {
117 if (DEBUG_MODE) {
118 console.log('resource =', resource)
119 console.log('subPath =', sub)
120 console.log('filters =', params)
121 }
122 var base = _path.join(this.config.version, sub)
123 if (Object.keys(params).length === 0) {
124 return base + '/' + resource
125 }
126
127 var q = qs.stringify(params).replace(/%2B/g, '+')
128 return base + '/' + resource + '/?' + q
129}
130
131/*
132 * httpRequest.
133 *
134 * @method {String} http method (GET/POST...)
135 * @url {String} url path to be used for the request
136 * @data {Object literal} additional data espacially for POST/PUT operations
137 * @callback -optional {Function} called on response from the server, or on error
138 *
139 * @return a promise triggering 'success' on response
140 * and error on error
141 */
142
143MailjetClient.prototype.httpRequest = function (method, url, data, callback) {
144
145 var req = request[method](url)
146 .set('user-agent', 'mailjet-api-v3-nodejs/' + this.version)
147
148 .set('Content-type', url.indexOf('text:plain') > -1
149 ? 'text/plain'
150 : 'application/json')
151
152 .auth(this.apiKey, this.apiSecret)
153
154 const payload = method === 'post' || method === 'put' ? data : {}
155
156 if (DEBUG_MODE) {
157 console.log('Final url: ' + url)
158 console.log('body: ' + payload)
159 }
160
161 if (this.testMode) {
162 return [url, payload]
163 }
164
165 if (method === 'delete') { method = 'del' }
166 if (method === 'post' || method === 'put') { req = req.send(data) }
167
168 return new Promise((resolve, reject) => {
169
170 const ret = (err, result) => typeof callback === 'function'
171 ? callback(err, result)
172 : err
173 ? reject(err)
174 : resolve(result)
175
176 req.end((err, result) => {
177 var body
178
179 try {
180 body = JSONb.parse(result.text)
181 } catch (e) {
182 body = {}
183 }
184
185 if (result && result.status && result.status > 210) {
186 const error = new Error('Unsuccessful')
187 error.ErrorMessage = body.ErrorMessage || (result.res.statusMessage)
188 error.statusCode = result.status
189 error.response = result
190 return ret(error)
191 }
192
193 return ret(null, {
194 response: result,
195 body
196 })
197 })
198 })
199}
200
201/*
202 *
203 * MailjetResource constructor
204 *
205 * This class creates a function that can be build through method chaining
206 *
207 * @method {String} http method
208 * @func {String} resource/path to be sent
209 * @context {MailjetClient[instance]} parent client
210 */
211function MailjetResource (method, func, context) {
212 this.base = func
213 this.callUrl = func
214
215 this.resource = func.toLowerCase()
216
217 this.lastAdded = RESOURCE
218 var self = context
219
220 /*
221 It can be REST or nothing if we only know the resource
222 */
223 this.subPath = (function () {
224 if (func.toLowerCase() !== 'send') {
225 return 'REST'
226 }
227 return ''
228 })()
229
230 /**
231 *
232 * result.
233 *
234 * @params (optional) {Object Littteral} parameters to be sent to the server
235 * @callback (optional) {Function} called on response or error
236 */
237 var that = this
238 this.result = function (params, callback) {
239 params = params || {}
240 if (typeof params === 'function') {
241 callback = params
242 params = {}
243 }
244
245 /*
246 We build the querystring depending on the parameters. if the user explicitly mentionned
247 a filters property, we pass it to the url
248 */
249 var path = self.path(that.callUrl, that.subPath, (function () {
250 if (params['filters']) {
251 var ret = params['filters']
252 delete params['filters']
253 return ret
254 } else if (method === 'get') {
255 return params
256 } else {
257 return {}
258 }
259 })())
260
261 that.callUrl = that.base
262 self.lastAdded = RESOURCE
263 return self.httpRequest(method, 'https://' + _path.join(self.config.url, path), params, callback)
264 }
265}
266
267/**
268 *
269 * id.
270 *
271 * Add an ID and prevent invalid id chaining
272 *
273 * @value {String/Number} append an id to the path
274 * @return the MailjetResource instance to allow method chaining
275 *
276 */
277MailjetResource.prototype.id = function (value) {
278 if (this.lastAdded === ID && DEBUG_MODE) {
279 console.warn('[WARNING] your request may fail due to invalid id chaining')
280 }
281
282 this.callUrl = _path.join(this.callUrl, value.toString())
283 this.lastAdded = ID
284 return this
285}
286
287/**
288 *
289 * action.
290 *
291 * Add an Action and prevent invalid action chaining
292 *
293 * @value {String} append an action to the path
294 * @return the MailjetResource instance to allow method chaining
295 *
296 */
297MailjetResource.prototype.action = function (name) {
298 if (this.lastAdded === ACTION && DEBUG_MODE) {
299 console.warn('[WARNING] your request may fail due to invalid action chaining')
300 }
301
302 this.callUrl = _path.join(this.callUrl, name)
303 this.action = name.toLowerCase()
304
305 this.lastAdded = ACTION
306
307 if (this.action.toLowerCase() === 'csvdata') {
308 this.action = 'csvdata/text:plain'
309 } else if (this.action.toLowerCase() === 'csverror') {
310 this.action = 'csverror/text:csv'
311 }
312
313 var self = this
314 this.subPath = (function () {
315 if (self.resource === 'contactslist' && self.action === 'csvdata/text:plain' ||
316 self.resource === 'batchjob' && self.action === 'csverror/text:csv') {
317 return 'DATA'
318 } else {
319 return self.subPath
320 }
321 })()
322 return self
323}
324
325/**
326 *
327 * request.
328 *
329 * @parmas {Object literal} method parameters
330 * @callback (optional) {Function} triggered when done
331 *
332 * @return {String} the server response
333 */
334
335MailjetResource.prototype.request = function (params, callback) {
336 return this.result(params, callback)
337}
338
339/*
340 * post.
341 *
342 * @func {String} required Mailjet API function to be used (can contain a whole action path)
343 *
344 * @returns a function that make an httpRequest for each call
345 */
346MailjetClient.prototype.post = function (func) {
347 return new MailjetResource('post', func, this)
348}
349
350/*
351 * get.
352 *
353 * @func {String} required Mailjet API function to be used (can contain a whole action path)
354 *
355 * @returns a function that make an httpRequest for each call
356 */
357MailjetClient.prototype.get = function (func) {
358 return new MailjetResource('get', func, this)
359}
360
361/*
362 * delete.
363 *
364 * @func {String} required Mailjet API function to be used (can contain a whole action path)
365 *
366 * @returns a function that make an httpRequest for each call
367 */
368MailjetClient.prototype.delete = function (func) {
369 return new MailjetResource('delete', func, this)
370}
371
372/*
373 * put.
374 *
375 * @func {String} required Mailjet API function to be used (can contain a whole action path)
376 *
377 * @returns a function that make an httpRequest for each call
378 */
379MailjetClient.prototype.put = function (func) {
380 return new MailjetResource('put', func, this)
381}
382
383/*
384 * Exports the Mailjet client.
385 *
386 * you can require it like so:
387 * var mj = require ('./mailjet-client')
388 *
389 * or for the bleeding edge developpers out there:
390 * import mj from './mailjet-client'
391 */
392module.exports = MailjetClient