mailgun.js 4.0.0 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,4 @@
4
4
 
5
5
  /*! https://mths.be/punycode v1.3.2 by @mathias */
6
6
 
7
- /*! mailgun.js v3.7.3 */
7
+ /*! mailgun.js v4.1.1 */
@@ -1,15 +1,23 @@
1
- interface StatsOptions {
1
+ export interface Stat {
2
+ time: string | Date,
3
+ delivered: {
4
+ smtp: number,
5
+ http: number,
6
+ total: number
7
+ }
8
+ }
9
+
10
+ export interface StatsOptions {
2
11
  start: string | Date;
3
12
  end: string | Date;
4
13
  resolution: string;
5
- stats: {
6
- time: string | Date,
7
- delivered: {
8
- smtp: number,
9
- http: number,
10
- total: number
11
- }
12
- }[];
14
+ stats: Stat[];
13
15
  }
14
16
 
15
- export default StatsOptions;
17
+ export interface StatsQuery {
18
+ event: string | string[];
19
+ start: string | Date;
20
+ end: string | Date;
21
+ resolution: 'hour'| 'day' | 'month';
22
+ duration: string;
23
+ }
@@ -16,3 +16,10 @@ export interface UnsubscribeData {
16
16
  tags: any;
17
17
  created_at: string | Date;
18
18
  }
19
+
20
+ export interface WhiteListData {
21
+ type: string;
22
+ value: string;
23
+ reason: string;
24
+ createdAt: string | Date;
25
+ }
package/lib/ip-pools.ts CHANGED
@@ -16,12 +16,12 @@ export default class IpPoolsClient {
16
16
  }
17
17
 
18
18
  create(data: { name: string, description?: string, ips?: string[] }) {
19
- return this.request.post('/v1/ip_pools', data)
19
+ return this.request.postWithFD('/v1/ip_pools', data)
20
20
  .then((response: { body: { message: string, pool_id: string } }) => response?.body);
21
21
  }
22
22
 
23
23
  update(poolId: string, data: IpPoolUpdateData) : Promise<any> {
24
- return this.request.patch(`/v1/ip_pools/${poolId}`, data)
24
+ return this.request.patchWithFD(`/v1/ip_pools/${poolId}`, data)
25
25
  .then((response: { body: any }) => response?.body);
26
26
  }
27
27
 
package/lib/request.ts CHANGED
@@ -50,7 +50,7 @@ class Request {
50
50
  private url: string;
51
51
  private timeout: number;
52
52
  private headers: any;
53
- private formData: InputFormData;
53
+ private FormDataConstructor: InputFormData;
54
54
 
55
55
  constructor(options: RequestOptions, formData: InputFormData) {
56
56
  this.username = options.username;
@@ -58,7 +58,7 @@ class Request {
58
58
  this.url = options.url as string;
59
59
  this.timeout = options.timeout;
60
60
  this.headers = options.headers || {};
61
- this.formData = formData;
61
+ this.FormDataConstructor = formData;
62
62
  }
63
63
 
64
64
  async request(method: string, url: string, inputOptions?: any): Promise<APIResponse> {
@@ -165,50 +165,94 @@ class Request {
165
165
  return this.command('put', url, formData, params);
166
166
  }
167
167
 
168
+ patchWithFD(url: string, data: any): Promise<APIResponse> {
169
+ if (!data) {
170
+ throw new Error('Please provide data object');
171
+ }
172
+ const params: any = {
173
+ headers: { 'Content-Type': null }
174
+ };
175
+ const formData = this.createFormData(data);
176
+ return this.command('patch', url, formData, params);
177
+ }
178
+
168
179
  createFormData(data: any): NodeFormData | FormData {
180
+ const formData: NodeFormData | FormData = Object.keys(data)
181
+ .filter(function (key) { return data[key]; })
182
+ .reduce((formDataAcc: NodeFormData | FormData, key) => {
183
+ const fileKeys = ['attachment', 'inline', 'file'];
184
+ if (fileKeys.includes(key)) {
185
+ this.addFilesToFD(key, data[key], formDataAcc);
186
+ return formDataAcc;
187
+ }
188
+
189
+ if (key === 'message') { // mime message
190
+ this.addMimeDataToFD(key, data[key], formDataAcc);
191
+ return formDataAcc;
192
+ }
193
+
194
+ this.addCommonPropertyToFD(key, data[key], formDataAcc);
195
+ return formDataAcc;
196
+ }, new this.FormDataConstructor());
197
+ return formData;
198
+ }
199
+
200
+ private addMimeDataToFD(
201
+ key: string,
202
+ data: Buffer | Blob,
203
+ formDataInstance: NodeFormData | FormData
204
+ ): void {
205
+ if (isNodeFormData(formDataInstance)) {
206
+ if (Buffer.isBuffer(data)) {
207
+ formDataInstance.append(key, data, { filename: 'MimeMessage' });
208
+ }
209
+ } else {
210
+ formDataInstance.append(key, data as Blob, 'MimeMessage');
211
+ }
212
+ }
213
+
214
+ private addFilesToFD(
215
+ propertyName: string,
216
+ value: any,
217
+ formDataInstance: NodeFormData | FormData
218
+ ): void {
169
219
  const appendFileToFD = (
170
220
  key: string,
171
221
  obj: any,
172
- formDataInstance: NodeFormData | FormData
222
+ formData: NodeFormData | FormData
173
223
  ): void => {
174
224
  const isStreamData = isStream(obj);
175
225
  const objData = isStreamData ? obj : obj.data;
226
+ // getAttachmentOptions should be called with obj parameter to prevent loosing filename
176
227
  const options = getAttachmentOptions(obj);
177
- if (isNodeFormData(formDataInstance)) {
178
- formDataInstance.append(key, objData, options);
228
+ if (isNodeFormData(formData)) {
229
+ formData.append(key, objData, options);
179
230
  return;
180
231
  }
181
- formDataInstance.append(key, objData, options.filename);
232
+ formData.append(key, objData, options.filename);
182
233
  };
183
234
 
184
- const formData: NodeFormData | FormData = Object.keys(data)
185
- .filter(function (key) { return data[key]; })
186
- .reduce((formDataAcc: NodeFormData | FormData, key) => {
187
- if (key === 'attachment' || key === 'inline' || key === 'file') {
188
- const obj = data[key];
189
-
190
- if (Array.isArray(obj)) {
191
- obj.forEach(function (item) {
192
- appendFileToFD(key, item, formDataAcc);
193
- });
194
- } else {
195
- appendFileToFD(key, obj, formDataAcc);
196
- }
197
-
198
- return formDataAcc;
199
- }
235
+ if (Array.isArray(value)) {
236
+ value.forEach(function (item) {
237
+ appendFileToFD(propertyName, item, formDataInstance);
238
+ });
239
+ } else {
240
+ appendFileToFD(propertyName, value, formDataInstance);
241
+ }
242
+ }
200
243
 
201
- if (Array.isArray(data[key])) {
202
- data[key].forEach(function (item: any) {
203
- formDataAcc.append(key, item);
204
- });
205
- } else if (data[key] != null) {
206
- formDataAcc.append(key, data[key]);
207
- }
208
- return formDataAcc;
209
- // eslint-disable-next-line new-cap
210
- }, new this.formData());
211
- return formData;
244
+ private addCommonPropertyToFD(
245
+ key: string,
246
+ value: any,
247
+ formDataAcc: NodeFormData | FormData
248
+ ): void {
249
+ if (Array.isArray(value)) {
250
+ value.forEach(function (item: any) {
251
+ formDataAcc.append(key, item);
252
+ });
253
+ } else if (value != null) {
254
+ formDataAcc.append(key, value);
255
+ }
212
256
  }
213
257
 
214
258
  put(url: string, data: any, options?: any): Promise<APIResponse> {
package/lib/stats.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import urljoin from 'url-join';
2
2
  import Request from './request';
3
- import StatsOptions from './interfaces/StatsOptions';
3
+ import { StatsQuery, StatsOptions, Stat } from './interfaces/StatsOptions';
4
4
 
5
5
  class Stats {
6
6
  start: Date;
7
7
  end: Date;
8
8
  resolution: string;
9
- stats: any;
9
+ stats: Stat[];
10
10
 
11
11
  constructor(data: StatsOptions) {
12
12
  this.start = new Date(data.start);
13
13
  this.end = new Date(data.end);
14
14
  this.resolution = data.resolution;
15
- this.stats = data.stats.map(function (stat: { time: string | Date }) {
15
+ this.stats = data.stats.map(function (stat: Stat) {
16
16
  const res = { ...stat };
17
17
  res.time = new Date(stat.time);
18
18
  return res;
@@ -27,17 +27,36 @@ export default class StatsClient {
27
27
  this.request = request;
28
28
  }
29
29
 
30
- _parseStats(response: { body: StatsOptions }) {
30
+ private prepareSearchParams(query: StatsQuery): Array<Array<string>> {
31
+ let searchParams = [];
32
+ if (typeof query === 'object' && Object.keys(query).length) {
33
+ searchParams = Object.entries(query).reduce((arrayWithPairs, currentPair) => {
34
+ const [key, value] = currentPair;
35
+ if (Array.isArray(value) && value.length) {
36
+ const repeatedProperty = value.map((item) => [key, item]);
37
+ return [...arrayWithPairs, ...repeatedProperty];
38
+ }
39
+ arrayWithPairs.push([key, value]);
40
+ return arrayWithPairs;
41
+ }, []);
42
+ }
43
+
44
+ return searchParams;
45
+ }
46
+
47
+ _parseStats(response: { body: StatsOptions }): Stats {
31
48
  return new Stats(response.body);
32
49
  }
33
50
 
34
- getDomain(domain: string, query: any) {
35
- return this.request.get(urljoin('/v3', domain, 'stats/total'), query)
51
+ getDomain(domain: string, query?: StatsQuery): Promise<Stats> {
52
+ const searchParams = this.prepareSearchParams(query);
53
+ return this.request.get(urljoin('/v3', domain, 'stats/total'), searchParams)
36
54
  .then(this._parseStats);
37
55
  }
38
56
 
39
- getAccount(query: any) {
40
- return this.request.get('/v3/stats/total', query)
57
+ getAccount(query?: StatsQuery): Promise<Stats> {
58
+ const searchParams = this.prepareSearchParams(query);
59
+ return this.request.get('/v3/stats/total', searchParams)
41
60
  .then(this._parseStats);
42
61
  }
43
62
  }
@@ -3,7 +3,12 @@ import url from 'url';
3
3
  import urljoin from 'url-join';
4
4
 
5
5
  import Request from './request';
6
- import { BounceData, ComplaintData, UnsubscribeData } from './interfaces/Supressions';
6
+ import {
7
+ BounceData,
8
+ ComplaintData,
9
+ UnsubscribeData,
10
+ WhiteListData
11
+ } from './interfaces/Supressions';
7
12
 
8
13
  const createOptions = {
9
14
  headers: { 'Content-Type': 'application/json' }
@@ -51,7 +56,21 @@ class Unsubscribe {
51
56
  }
52
57
  }
53
58
 
54
- type TModel = typeof Bounce | typeof Complaint | typeof Unsubscribe;
59
+ class WhiteList {
60
+ type: string;
61
+ value: string;
62
+ reason: string;
63
+ createdAt: Date;
64
+
65
+ constructor(data: WhiteListData) {
66
+ this.type = 'whitelists';
67
+ this.value = data.value;
68
+ this.reason = data.reason;
69
+ this.createdAt = new Date(data.createdAt);
70
+ }
71
+ }
72
+
73
+ type TModel = typeof Bounce | typeof Complaint | typeof Unsubscribe | typeof WhiteList;
55
74
 
56
75
  export default class SuppressionClient {
57
76
  request: any;
@@ -59,6 +78,7 @@ export default class SuppressionClient {
59
78
  bounces: typeof Bounce;
60
79
  complaints: typeof Complaint;
61
80
  unsubscribes: typeof Unsubscribe;
81
+ whitelists: typeof WhiteList;
62
82
  };
63
83
 
64
84
  constructor(request: Request) {
@@ -66,7 +86,8 @@ export default class SuppressionClient {
66
86
  this.models = {
67
87
  bounces: Bounce,
68
88
  complaints: Complaint,
69
- unsubscribes: Unsubscribe
89
+ unsubscribes: Unsubscribe,
90
+ whitelists: WhiteList,
70
91
  };
71
92
  }
72
93
 
@@ -106,6 +127,12 @@ export default class SuppressionClient {
106
127
  return new Model(response.body);
107
128
  }
108
129
 
130
+ private createWhiteList(domain: string, data: any) {
131
+ return this.request
132
+ .postWithFD(urljoin('v3', domain, 'whitelists'), data, createOptions)
133
+ .then((response: { body: any }) => response.body);
134
+ }
135
+
109
136
  list(domain: string, type: string, query: any) {
110
137
  const model = (this.models as any)[type];
111
138
 
@@ -125,14 +152,18 @@ export default class SuppressionClient {
125
152
  create(domain: string, type: string, data: any) {
126
153
  // supports adding multiple suppressions by default
127
154
  let postData;
155
+ if (type === 'whitelists') {
156
+ return this.createWhiteList(domain, data);
157
+ }
158
+
128
159
  if (!Array.isArray(data)) {
129
160
  postData = [data];
130
161
  } else {
131
- postData = { ...data };
162
+ postData = [...data];
132
163
  }
133
164
 
134
165
  return this.request
135
- .post(urljoin('v3', domain, type), postData, createOptions)
166
+ .post(urljoin('v3', domain, type), JSON.stringify(postData), createOptions)
136
167
  .then((response: { body: any }) => response.body);
137
168
  }
138
169
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailgun.js",
3
- "version": "4.0.0",
3
+ "version": "4.1.2",
4
4
  "main": "dist/mailgun.node.js",
5
5
  "browser": "dist/mailgun.web.js",
6
6
  "types": "dist/index.d.ts",
@@ -105,7 +105,8 @@
105
105
  "commitUrlFormat": "https://github.com/mailgun/mailgun.js/commits/{{hash}}",
106
106
  "compareUrlFormat": "https://github.com/mailgun/mailgun.js/compare/{{previousTag}}...{{currentTag}}",
107
107
  "scripts": {
108
- "prerelease": "npm test && webpack --config ./webpack/webpack.release.config.js --progress --color && git add -A dist"
108
+ "prerelease": "npm test && webpack --config ./webpack/webpack.release.config.js --progress --color && git add -A dist",
109
+ "posttag": "git push && git push --tags && rm -rf build"
109
110
  }
110
111
  }
111
112
  }
@@ -4,7 +4,7 @@ import nock from 'nock';
4
4
  import Request from '../lib/request';
5
5
  import StatsClient from '../lib/stats';
6
6
  import RequestOptions from '../lib/interfaces/RequestOptions';
7
- import StatsOptions from '../lib/interfaces/StatsOptions';
7
+ import { StatsOptions } from '../lib/interfaces/StatsOptions';
8
8
  import { InputFormData } from '../lib/interfaces/IFormData';
9
9
 
10
10
  describe('StatsClient', function () {