@metigan/angular 1.0.0

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.
Files changed (48) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +581 -0
  3. package/dist/LICENSE +22 -0
  4. package/dist/README.md +581 -0
  5. package/dist/esm2022/metigan-angular.mjs +5 -0
  6. package/dist/esm2022/public-api.mjs +21 -0
  7. package/dist/esm2022/src/lib/audiences.service.mjs +157 -0
  8. package/dist/esm2022/src/lib/config.mjs +30 -0
  9. package/dist/esm2022/src/lib/contacts.service.mjs +267 -0
  10. package/dist/esm2022/src/lib/email.service.mjs +267 -0
  11. package/dist/esm2022/src/lib/errors.mjs +40 -0
  12. package/dist/esm2022/src/lib/forms.service.mjs +180 -0
  13. package/dist/esm2022/src/lib/http-client.service.mjs +111 -0
  14. package/dist/esm2022/src/lib/metigan.module.mjs +67 -0
  15. package/dist/esm2022/src/lib/metigan.service.mjs +72 -0
  16. package/dist/esm2022/src/lib/templates.service.mjs +85 -0
  17. package/dist/esm2022/src/lib/types.mjs +6 -0
  18. package/dist/fesm2022/metigan-angular.mjs +1241 -0
  19. package/dist/fesm2022/metigan-angular.mjs.map +1 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/public-api.d.ts +15 -0
  22. package/dist/src/lib/audiences.service.d.ts +62 -0
  23. package/dist/src/lib/config.d.ts +28 -0
  24. package/dist/src/lib/contacts.service.d.ts +80 -0
  25. package/dist/src/lib/email.service.d.ts +44 -0
  26. package/dist/src/lib/errors.d.ts +24 -0
  27. package/dist/src/lib/forms.service.d.ts +67 -0
  28. package/dist/src/lib/http-client.service.d.ts +46 -0
  29. package/dist/src/lib/metigan.module.d.ts +27 -0
  30. package/dist/src/lib/metigan.service.d.ts +27 -0
  31. package/dist/src/lib/templates.service.d.ts +36 -0
  32. package/dist/src/lib/types.d.ts +329 -0
  33. package/examples/basic.component.ts +113 -0
  34. package/ng-package.json +8 -0
  35. package/package.json +68 -0
  36. package/public-api.ts +26 -0
  37. package/src/lib/audiences.service.ts +230 -0
  38. package/src/lib/config.ts +35 -0
  39. package/src/lib/contacts.service.ts +377 -0
  40. package/src/lib/email.service.ts +286 -0
  41. package/src/lib/errors.ts +45 -0
  42. package/src/lib/forms.service.ts +263 -0
  43. package/src/lib/http-client.service.ts +156 -0
  44. package/src/lib/metigan.module.ts +55 -0
  45. package/src/lib/metigan.service.ts +80 -0
  46. package/src/lib/templates.service.ts +103 -0
  47. package/src/lib/types.ts +398 -0
  48. package/tsconfig.json +38 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Metigan Audiences Service
3
+ * Service for audience/list management
4
+ */
5
+
6
+ import { Injectable } from '@angular/core';
7
+ import { Observable, throwError } from 'rxjs';
8
+ import { HttpParams } from '@angular/common/http';
9
+ import { MetiganHttpClient } from './http-client.service';
10
+ import { API_URL, DEFAULT_TIMEOUT, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY } from './config';
11
+ import { ValidationError, MetiganError } from './errors';
12
+ import {
13
+ Audience,
14
+ CreateAudienceOptions,
15
+ UpdateAudienceOptions,
16
+ AudienceListResponse,
17
+ AudienceStats,
18
+ PaginationOptions
19
+ } from './types';
20
+
21
+ @Injectable({
22
+ providedIn: 'root'
23
+ })
24
+ export class MetiganAudiencesService {
25
+ private apiKey: string = '';
26
+ private apiUrl: string = API_URL;
27
+ private timeout: number = DEFAULT_TIMEOUT;
28
+ private retryCount: number = DEFAULT_RETRY_COUNT;
29
+ private retryDelay: number = DEFAULT_RETRY_DELAY;
30
+
31
+ constructor(private http: MetiganHttpClient) {}
32
+
33
+ /**
34
+ * Initialize the service with API key and options
35
+ */
36
+ initialize(apiKey: string, options?: { apiUrl?: string; timeout?: number; retryCount?: number; retryDelay?: number }): void {
37
+ if (!apiKey) {
38
+ throw new MetiganError('API key is required');
39
+ }
40
+
41
+ this.apiKey = apiKey;
42
+ this.apiUrl = options?.apiUrl || API_URL;
43
+ this.timeout = options?.timeout || DEFAULT_TIMEOUT;
44
+ this.retryCount = options?.retryCount || DEFAULT_RETRY_COUNT;
45
+ this.retryDelay = options?.retryDelay || DEFAULT_RETRY_DELAY;
46
+ }
47
+
48
+ /**
49
+ * Get default headers
50
+ */
51
+ private getHeaders() {
52
+ return {
53
+ 'Content-Type': 'application/json',
54
+ 'x-api-key': this.apiKey,
55
+ 'User-Agent': 'AngularSDK/1.0'
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Create a new audience
61
+ */
62
+ create(options: CreateAudienceOptions): Observable<Audience> {
63
+ if (!this.apiKey) {
64
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
65
+ }
66
+
67
+ if (!options.name) {
68
+ return throwError(() => new ValidationError('Audience name is required'));
69
+ }
70
+
71
+ return this.http.post<Audience>(
72
+ `${this.apiUrl}/api/audiences`,
73
+ options,
74
+ { headers: this.getHeaders() },
75
+ this.retryCount,
76
+ this.retryDelay
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Get audience by ID
82
+ */
83
+ get(audienceId: string): Observable<Audience> {
84
+ if (!this.apiKey) {
85
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
86
+ }
87
+
88
+ if (!audienceId) {
89
+ return throwError(() => new ValidationError('Audience ID is required'));
90
+ }
91
+
92
+ return this.http.get<Audience>(
93
+ `${this.apiUrl}/api/audiences/${audienceId}`,
94
+ { headers: this.getHeaders() },
95
+ this.retryCount,
96
+ this.retryDelay
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Update an audience
102
+ */
103
+ update(audienceId: string, options: UpdateAudienceOptions): Observable<Audience> {
104
+ if (!this.apiKey) {
105
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
106
+ }
107
+
108
+ if (!audienceId) {
109
+ return throwError(() => new ValidationError('Audience ID is required'));
110
+ }
111
+
112
+ return this.http.put<Audience>(
113
+ `${this.apiUrl}/api/audiences/${audienceId}`,
114
+ options,
115
+ { headers: this.getHeaders() },
116
+ this.retryCount,
117
+ this.retryDelay
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Delete an audience
123
+ */
124
+ delete(audienceId: string): Observable<void> {
125
+ if (!this.apiKey) {
126
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
127
+ }
128
+
129
+ if (!audienceId) {
130
+ return throwError(() => new ValidationError('Audience ID is required'));
131
+ }
132
+
133
+ return this.http.delete<void>(
134
+ `${this.apiUrl}/api/audiences/${audienceId}`,
135
+ { headers: this.getHeaders() },
136
+ this.retryCount,
137
+ this.retryDelay
138
+ );
139
+ }
140
+
141
+ /**
142
+ * List all audiences
143
+ */
144
+ list(options?: PaginationOptions): Observable<AudienceListResponse> {
145
+ if (!this.apiKey) {
146
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
147
+ }
148
+
149
+ let params = new HttpParams();
150
+ if (options?.page) {
151
+ params = params.set('page', options.page.toString());
152
+ }
153
+ if (options?.limit) {
154
+ params = params.set('limit', options.limit.toString());
155
+ }
156
+
157
+ return this.http.get<AudienceListResponse>(
158
+ `${this.apiUrl}/api/audiences`,
159
+ {
160
+ headers: this.getHeaders(),
161
+ params: params
162
+ },
163
+ this.retryCount,
164
+ this.retryDelay
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Get audience statistics
170
+ */
171
+ getStats(audienceId: string): Observable<AudienceStats> {
172
+ if (!this.apiKey) {
173
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
174
+ }
175
+
176
+ if (!audienceId) {
177
+ return throwError(() => new ValidationError('Audience ID is required'));
178
+ }
179
+
180
+ return this.http.get<AudienceStats>(
181
+ `${this.apiUrl}/api/audiences/${audienceId}/stats`,
182
+ { headers: this.getHeaders() },
183
+ this.retryCount,
184
+ this.retryDelay
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Clean audience (remove bounced/unsubscribed contacts)
190
+ */
191
+ clean(audienceId: string): Observable<{ removed: number }> {
192
+ if (!this.apiKey) {
193
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
194
+ }
195
+
196
+ if (!audienceId) {
197
+ return throwError(() => new ValidationError('Audience ID is required'));
198
+ }
199
+
200
+ return this.http.post<{ removed: number }>(
201
+ `${this.apiUrl}/api/audiences/${audienceId}/clean`,
202
+ {},
203
+ { headers: this.getHeaders() },
204
+ this.retryCount,
205
+ this.retryDelay
206
+ );
207
+ }
208
+
209
+ /**
210
+ * Merge audiences (source into target)
211
+ */
212
+ merge(sourceAudienceId: string, targetAudienceId: string): Observable<Audience> {
213
+ if (!this.apiKey) {
214
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
215
+ }
216
+
217
+ if (!sourceAudienceId || !targetAudienceId) {
218
+ return throwError(() => new ValidationError('Source and target audience IDs are required'));
219
+ }
220
+
221
+ return this.http.post<Audience>(
222
+ `${this.apiUrl}/api/audiences/${sourceAudienceId}/merge`,
223
+ { targetAudienceId },
224
+ { headers: this.getHeaders() },
225
+ this.retryCount,
226
+ this.retryDelay
227
+ );
228
+ }
229
+ }
230
+
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Metigan Library Configuration
3
+ * Central configuration file for all modules
4
+ */
5
+
6
+ /**
7
+ * Default API URL for Metigan services
8
+ * All API calls are routed through this single endpoint
9
+ *
10
+ * Can be overridden with METIGAN_API_URL environment variable for testing
11
+ */
12
+ export const API_URL = (typeof process !== 'undefined' && process.env?.['METIGAN_API_URL'])
13
+ || 'https://api.metigan.com';
14
+
15
+ /**
16
+ * SDK Version
17
+ */
18
+ export const SDK_VERSION = '1.0.0';
19
+
20
+ /**
21
+ * Default timeout for API requests (in milliseconds)
22
+ */
23
+ export const DEFAULT_TIMEOUT = 30000;
24
+
25
+ /**
26
+ * Default retry configuration
27
+ */
28
+ export const DEFAULT_RETRY_COUNT = 3;
29
+ export const DEFAULT_RETRY_DELAY = 1000;
30
+
31
+ /**
32
+ * Maximum file size for attachments (7MB)
33
+ */
34
+ export const MAX_FILE_SIZE = 7 * 1024 * 1024;
35
+
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Metigan Contacts Service
3
+ * Service for contact/subscriber management
4
+ */
5
+
6
+ import { Injectable } from '@angular/core';
7
+ import { Observable, throwError } from 'rxjs';
8
+ import { HttpParams } from '@angular/common/http';
9
+ import { MetiganHttpClient } from './http-client.service';
10
+ import { API_URL, DEFAULT_TIMEOUT, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY } from './config';
11
+ import { ValidationError, MetiganError } from './errors';
12
+ import {
13
+ Contact,
14
+ CreateContactOptions,
15
+ UpdateContactOptions,
16
+ ContactListFilters,
17
+ ContactListResponse,
18
+ BulkContactResult
19
+ } from './types';
20
+
21
+ @Injectable({
22
+ providedIn: 'root'
23
+ })
24
+ export class MetiganContactsService {
25
+ private apiKey: string = '';
26
+ private apiUrl: string = API_URL;
27
+ private timeout: number = DEFAULT_TIMEOUT;
28
+ private retryCount: number = DEFAULT_RETRY_COUNT;
29
+ private retryDelay: number = DEFAULT_RETRY_DELAY;
30
+
31
+ constructor(private http: MetiganHttpClient) {}
32
+
33
+ /**
34
+ * Initialize the service with API key and options
35
+ */
36
+ initialize(apiKey: string, options?: { apiUrl?: string; timeout?: number; retryCount?: number; retryDelay?: number }): void {
37
+ if (!apiKey) {
38
+ throw new MetiganError('API key is required');
39
+ }
40
+
41
+ this.apiKey = apiKey;
42
+ this.apiUrl = options?.apiUrl || API_URL;
43
+ this.timeout = options?.timeout || DEFAULT_TIMEOUT;
44
+ this.retryCount = options?.retryCount || DEFAULT_RETRY_COUNT;
45
+ this.retryDelay = options?.retryDelay || DEFAULT_RETRY_DELAY;
46
+ }
47
+
48
+ /**
49
+ * Get default headers
50
+ */
51
+ private getHeaders() {
52
+ return {
53
+ 'Content-Type': 'application/json',
54
+ 'x-api-key': this.apiKey,
55
+ 'User-Agent': 'AngularSDK/1.0'
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Validate email format
61
+ */
62
+ private validateEmail(email: string): boolean {
63
+ if (!email || typeof email !== 'string') return false;
64
+ const parts = email.split('@');
65
+ if (parts.length !== 2) return false;
66
+ if (parts[0].length === 0) return false;
67
+ const domainParts = parts[1].split('.');
68
+ if (domainParts.length < 2) return false;
69
+ if (domainParts.some(part => part.length === 0)) return false;
70
+ return true;
71
+ }
72
+
73
+ /**
74
+ * Create a new contact
75
+ */
76
+ create(options: CreateContactOptions): Observable<Contact> {
77
+ if (!this.apiKey) {
78
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
79
+ }
80
+
81
+ if (!options.email || !this.validateEmail(options.email)) {
82
+ return throwError(() => new ValidationError('Valid email is required'));
83
+ }
84
+
85
+ if (!options.audienceId) {
86
+ return throwError(() => new ValidationError('Audience ID is required'));
87
+ }
88
+
89
+ return this.http.post<Contact>(
90
+ `${this.apiUrl}/api/contacts`,
91
+ options,
92
+ { headers: this.getHeaders() },
93
+ this.retryCount,
94
+ this.retryDelay
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Get contact by ID
100
+ */
101
+ get(contactId: string): Observable<Contact> {
102
+ if (!this.apiKey) {
103
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
104
+ }
105
+
106
+ if (!contactId) {
107
+ return throwError(() => new ValidationError('Contact ID is required'));
108
+ }
109
+
110
+ return this.http.get<Contact>(
111
+ `${this.apiUrl}/api/contacts/${contactId}`,
112
+ { headers: this.getHeaders() },
113
+ this.retryCount,
114
+ this.retryDelay
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Get contact by email
120
+ */
121
+ getByEmail(email: string, audienceId: string): Observable<Contact> {
122
+ if (!this.apiKey) {
123
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
124
+ }
125
+
126
+ if (!email || !this.validateEmail(email)) {
127
+ return throwError(() => new ValidationError('Valid email is required'));
128
+ }
129
+
130
+ if (!audienceId) {
131
+ return throwError(() => new ValidationError('Audience ID is required'));
132
+ }
133
+
134
+ let params = new HttpParams();
135
+ params = params.set('email', email);
136
+ params = params.set('audienceId', audienceId);
137
+
138
+ return this.http.get<Contact>(
139
+ `${this.apiUrl}/api/contacts`,
140
+ {
141
+ headers: this.getHeaders(),
142
+ params: params
143
+ },
144
+ this.retryCount,
145
+ this.retryDelay
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Update a contact
151
+ */
152
+ update(contactId: string, options: UpdateContactOptions): Observable<Contact> {
153
+ if (!this.apiKey) {
154
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
155
+ }
156
+
157
+ if (!contactId) {
158
+ return throwError(() => new ValidationError('Contact ID is required'));
159
+ }
160
+
161
+ return this.http.put<Contact>(
162
+ `${this.apiUrl}/api/contacts/${contactId}`,
163
+ options,
164
+ { headers: this.getHeaders() },
165
+ this.retryCount,
166
+ this.retryDelay
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Delete a contact
172
+ */
173
+ delete(contactId: string): Observable<void> {
174
+ if (!this.apiKey) {
175
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
176
+ }
177
+
178
+ if (!contactId) {
179
+ return throwError(() => new ValidationError('Contact ID is required'));
180
+ }
181
+
182
+ return this.http.delete<void>(
183
+ `${this.apiUrl}/api/contacts/${contactId}`,
184
+ { headers: this.getHeaders() },
185
+ this.retryCount,
186
+ this.retryDelay
187
+ );
188
+ }
189
+
190
+ /**
191
+ * List contacts with filters
192
+ */
193
+ list(filters?: ContactListFilters): Observable<ContactListResponse> {
194
+ if (!this.apiKey) {
195
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
196
+ }
197
+
198
+ let params = new HttpParams();
199
+ if (filters?.audienceId) {
200
+ params = params.set('audienceId', filters.audienceId);
201
+ }
202
+ if (filters?.status) {
203
+ params = params.set('status', filters.status);
204
+ }
205
+ if (filters?.tag) {
206
+ params = params.set('tag', filters.tag);
207
+ }
208
+ if (filters?.search) {
209
+ params = params.set('search', filters.search);
210
+ }
211
+ if (filters?.page) {
212
+ params = params.set('page', filters.page.toString());
213
+ }
214
+ if (filters?.limit) {
215
+ params = params.set('limit', filters.limit.toString());
216
+ }
217
+
218
+ return this.http.get<ContactListResponse>(
219
+ `${this.apiUrl}/api/contacts`,
220
+ {
221
+ headers: this.getHeaders(),
222
+ params: params
223
+ },
224
+ this.retryCount,
225
+ this.retryDelay
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Search contacts
231
+ */
232
+ search(query: string, audienceId?: string): Observable<Contact[]> {
233
+ if (!this.apiKey) {
234
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
235
+ }
236
+
237
+ if (!query) {
238
+ return throwError(() => new ValidationError('Search query is required'));
239
+ }
240
+
241
+ let params = new HttpParams();
242
+ params = params.set('search', query);
243
+ if (audienceId) {
244
+ params = params.set('audienceId', audienceId);
245
+ }
246
+
247
+ return this.http.get<Contact[]>(
248
+ `${this.apiUrl}/api/contacts/search`,
249
+ {
250
+ headers: this.getHeaders(),
251
+ params: params
252
+ },
253
+ this.retryCount,
254
+ this.retryDelay
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Bulk import contacts
260
+ */
261
+ bulkImport(contacts: CreateContactOptions[], audienceId: string): Observable<BulkContactResult> {
262
+ if (!this.apiKey) {
263
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
264
+ }
265
+
266
+ if (!contacts || contacts.length === 0) {
267
+ return throwError(() => new ValidationError('Contacts array is required'));
268
+ }
269
+
270
+ if (!audienceId) {
271
+ return throwError(() => new ValidationError('Audience ID is required'));
272
+ }
273
+
274
+ return this.http.post<BulkContactResult>(
275
+ `${this.apiUrl}/api/contacts/bulk`,
276
+ { contacts, audienceId },
277
+ { headers: this.getHeaders() },
278
+ this.retryCount,
279
+ this.retryDelay
280
+ );
281
+ }
282
+
283
+ /**
284
+ * Subscribe a contact
285
+ */
286
+ subscribe(contactId: string): Observable<Contact> {
287
+ if (!this.apiKey) {
288
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
289
+ }
290
+
291
+ if (!contactId) {
292
+ return throwError(() => new ValidationError('Contact ID is required'));
293
+ }
294
+
295
+ return this.http.post<Contact>(
296
+ `${this.apiUrl}/api/contacts/${contactId}/subscribe`,
297
+ {},
298
+ { headers: this.getHeaders() },
299
+ this.retryCount,
300
+ this.retryDelay
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Unsubscribe a contact
306
+ */
307
+ unsubscribe(contactId: string): Observable<Contact> {
308
+ if (!this.apiKey) {
309
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
310
+ }
311
+
312
+ if (!contactId) {
313
+ return throwError(() => new ValidationError('Contact ID is required'));
314
+ }
315
+
316
+ return this.http.post<Contact>(
317
+ `${this.apiUrl}/api/contacts/${contactId}/unsubscribe`,
318
+ {},
319
+ { headers: this.getHeaders() },
320
+ this.retryCount,
321
+ this.retryDelay
322
+ );
323
+ }
324
+
325
+ /**
326
+ * Add tags to a contact
327
+ */
328
+ addTags(contactId: string, tags: string[]): Observable<Contact> {
329
+ if (!this.apiKey) {
330
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
331
+ }
332
+
333
+ if (!contactId) {
334
+ return throwError(() => new ValidationError('Contact ID is required'));
335
+ }
336
+
337
+ if (!tags || tags.length === 0) {
338
+ return throwError(() => new ValidationError('Tags array is required'));
339
+ }
340
+
341
+ return this.http.post<Contact>(
342
+ `${this.apiUrl}/api/contacts/${contactId}/tags`,
343
+ { tags },
344
+ { headers: this.getHeaders() },
345
+ this.retryCount,
346
+ this.retryDelay
347
+ );
348
+ }
349
+
350
+ /**
351
+ * Remove tags from a contact
352
+ */
353
+ removeTags(contactId: string, tags: string[]): Observable<Contact> {
354
+ if (!this.apiKey) {
355
+ return throwError(() => new MetiganError('Service not initialized. Call initialize() first.'));
356
+ }
357
+
358
+ if (!contactId) {
359
+ return throwError(() => new ValidationError('Contact ID is required'));
360
+ }
361
+
362
+ if (!tags || tags.length === 0) {
363
+ return throwError(() => new ValidationError('Tags array is required'));
364
+ }
365
+
366
+ return this.http.delete<Contact>(
367
+ `${this.apiUrl}/api/contacts/${contactId}/tags`,
368
+ {
369
+ headers: this.getHeaders(),
370
+ params: { tags: tags.join(',') }
371
+ },
372
+ this.retryCount,
373
+ this.retryDelay
374
+ );
375
+ }
376
+ }
377
+